Typescript: 建议:正则表达式验证的字符串类型

创建于 2016-01-22  ·  146评论  ·  资料来源: microsoft/TypeScript

在某些情况下,属性不能只是任何字符串(或一组字符串),而是需要匹配模式。

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

JavaScript 中的常见做法是以 css 符号存储颜色值,例如在 DOM 节点或各种 3rd 方库的 css 样式反射中。

你怎么认为?

Literal Types Needs Proposal Suggestion

最有用的评论

设计方案

在很多情况下,开发人员需要更多指定的值而不是一个字符串,但不能将它们枚举为简单字符串文字的联合,例如 css 颜色、电子邮件、电话号码、ZipCode、 swagger 扩展等。即使是通常的json 模式规范用于描述 JSON 对象的模式具有模式属性,在 TS 类型系统方面可以称为regex-validated string typeregex-validated string type of index

目标

为开发人员提供更接近他们常用的 JSON Schema 的类型系统,并防止他们在需要时忘记字符串验证检查。

句法概述

此功能的实现包括 4 个部分:

正则表达式验证类型

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;

正则表达式验证的变量类型

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

和相同的,但更具可读性

let fontColor: CssColor;

正则表达式验证的索引变量类型

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

和相同的,但更具可读性

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

变量类型的类型保护

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

和一样

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

并使用定义的类型以获得更好的可读性

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

与...一样

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

索引类型的类型保护

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

与...一样

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
}

并使用定义的类型以获得更好的可读性

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

与...一样

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

语义概述

作业

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

不幸的是,由于本文,我们无法检查一个正则表达式是否是另一个正则表达式的子类型,而不会对性能造成严重影响。 所以应该限制。 但还有下一个解决方法:

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

不幸的是,将string变量分配给regex-validated变量也应该受到限制,因为在编译时不能保证它会匹配正则表达式。

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

但是,我们能够使用显式类型转换或类型警卫如图所示这里。 二是推荐。
幸运的是,这不是字符串文字的情况,因为在使用它们时,我们能够检查它的值是否与正则表达式匹配。

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

索引的类型缩小

对于regex-validated type索引的简单情况,请参阅Type gurard for index type
但可能有更复杂的情况:

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

文字没有这样的问题:

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

但是对于变量,最好的选择是使用类型保护,就像在下一个更现实的例子中一样:

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

但是,如果我们对Gmail类型使用更好的定义,它将有另一种类型缩小:

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

联合和交叉

实际上普通类型和regex-validated类型确实不同,所以我们需要规则如何正确处理它们的并集和交集。

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
}

泛型

泛型没有特殊情况,所以regex-validated类型可以像普通类型一样与泛型一起使用。
对于具有如下约束的泛型, regex-validated类型的行为类似于字符串:

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

发出概览

与通常的类型不同, regex-validated对发射有一些影响:

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

将编译为:

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

兼容性概述

此功能没有兼容性问题,因为只有可能会破坏它的情况,这与regex-validated类型发出的影响与通常类型不同,因此这是有效的 TS 代码:

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

当下面的代码不是:

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

但是第二个已经无效,但由于另一个原因(类型声明错误)。
所以现在我们必须限制名称与类型相同的变量的声明,以防这种类型是regex-validated

聚苯乙烯

随意指出我可能错过的事情。 如果你喜欢这个提议,我可以尝试创建覆盖它的测试并将它们添加为 PR。

所有146条评论

是的,我已经看到了这个通过绝对类型,. 甚至我们可以在服务层中使用

主要问题是:

  • 目前还不清楚如何很好地组合这些。 如果我想要"cat""dog""fish"的逗号分隔列表,那么我需要编写类似/dog|cat|fish(,(dog|cat|fish))*/

    • 如果我已经有描述"cat""dog " 和"fish"字符串文字类型的类型,我该如何将它们集成到这个正则表达式中?

    • 显然这里有重复,这是不可取的。 也许修复上一个问题会使这更容易。

  • 非标准扩展使这种不确定。

对此,ZipCode、SSN、ONet 以及许多其他用例提供了巨大的 +1。

我遇到了同样的问题,我发现它还没有实现,也许这个解决方法会有所帮助:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in​​-typescript

正如@mhegazy建议的那样,我会将我的建议 (#8665) 放在这里。 在类型声明中允许简单的验证函数怎么样? 类似的东西:

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

类型可以接受的值将由函数参数类型和函数评估本身决定。 这也可以解决#7982。

@rylphs +1 这将使 TypeScript 非常强大

子类型如何与 _regex 验证的字符串类型一起使用?

let a: RegExType_1
let b: RegExType_2

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

其中RegExType_1RegExType_2是 _regex 验证的字符串类型_。

编辑:看起来这个问题可以在多项式时间内解决(请参阅正则表达式的包含问题)。

也有助于 TypeStyle : https :

在 JSX 中, @RyanCavanaugh和我看到人们添加了aria- (可能还有data- )属性。 有人实际上在绝对类型中添加了一个字符串索引签名作为包罗万象。 为此,新的索引签名会有所帮助。

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

设计方案

在很多情况下,开发人员需要更多指定的值而不是一个字符串,但不能将它们枚举为简单字符串文字的联合,例如 css 颜色、电子邮件、电话号码、ZipCode、 swagger 扩展等。即使是通常的json 模式规范用于描述 JSON 对象的模式具有模式属性,在 TS 类型系统方面可以称为regex-validated string typeregex-validated string type of index

目标

为开发人员提供更接近他们常用的 JSON Schema 的类型系统,并防止他们在需要时忘记字符串验证检查。

句法概述

此功能的实现包括 4 个部分:

正则表达式验证类型

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;

正则表达式验证的变量类型

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

和相同的,但更具可读性

let fontColor: CssColor;

正则表达式验证的索引变量类型

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

和相同的,但更具可读性

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

变量类型的类型保护

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

和一样

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

并使用定义的类型以获得更好的可读性

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

与...一样

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

索引类型的类型保护

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

与...一样

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
}

并使用定义的类型以获得更好的可读性

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

与...一样

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

语义概述

作业

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

不幸的是,由于本文,我们无法检查一个正则表达式是否是另一个正则表达式的子类型,而不会对性能造成严重影响。 所以应该限制。 但还有下一个解决方法:

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

不幸的是,将string变量分配给regex-validated变量也应该受到限制,因为在编译时不能保证它会匹配正则表达式。

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

但是,我们能够使用显式类型转换或类型警卫如图所示这里。 二是推荐。
幸运的是,这不是字符串文字的情况,因为在使用它们时,我们能够检查它的值是否与正则表达式匹配。

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

索引的类型缩小

对于regex-validated type索引的简单情况,请参阅Type gurard for index type
但可能有更复杂的情况:

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

文字没有这样的问题:

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

但是对于变量,最好的选择是使用类型保护,就像在下一个更现实的例子中一样:

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

但是,如果我们对Gmail类型使用更好的定义,它将有另一种类型缩小:

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

联合和交叉

实际上普通类型和regex-validated类型确实不同,所以我们需要规则如何正确处理它们的并集和交集。

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
}

泛型

泛型没有特殊情况,所以regex-validated类型可以像普通类型一样与泛型一起使用。
对于具有如下约束的泛型, regex-validated类型的行为类似于字符串:

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

发出概览

与通常的类型不同, regex-validated对发射有一些影响:

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

将编译为:

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

兼容性概述

此功能没有兼容性问题,因为只有可能会破坏它的情况,这与regex-validated类型发出的影响与通常类型不同,因此这是有效的 TS 代码:

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

当下面的代码不是:

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

但是第二个已经无效,但由于另一个原因(类型声明错误)。
所以现在我们必须限制名称与类型相同的变量的声明,以防这种类型是regex-validated

聚苯乙烯

随意指出我可能错过的事情。 如果你喜欢这个提议,我可以尝试创建覆盖它的测试并将它们添加为 PR。

我忘记指出regex-validated类型的交集和并集的一些情况,但我已经在最新的测试用例中描述了它们。 我应该更新Design proposal以反映那个小变化吗?

@Igmat ,关于你的设计方案的问题:你能详细说明一下发射概述吗? 为什么需要发出经过正则表达式验证的类型? 据我所知,其他类型不支持运行时检查......我错过了什么吗?

@alexanderbird ,是的,任何其他类型对发射都没有影响。 起初,我认为regex-validated也会这样做,所以我开始创建提案并尝试使用建议的语法。
第一种方法是这样的:

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

和这个:

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

没关系,不需要发出更改,因为"#000"可以在编译时检查。
但是我们还必须处理从stringregex-validated类型的缩小以使其有用。 所以我在之前的两个设置中都考虑过这个:

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

所以它也对发射没有影响,看起来还可以,除了正则表达式不是很可读并且必须在所有地方复制,所以用户很容易犯错误。 但在这种特殊情况下,它似乎仍然比改变type工作方式要好。
但后来我意识到这个东西:

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

是一场噩梦。 它甚至没有交集和联合。 所以为了避免发生这样的事情,我们必须稍微改变type发射,如提案所示。

@DanielRosenwasser ,能否请您对此提案提供一些反馈? 如果可能的话,还有这里引用的测试吗?
我真的很想帮助实现这个功能,但它需要很多时间( tsc是一个非常复杂的项目,我仍然需要了解它内部是如何工作的),我不知道是此提案已准备好实施,否则您将因其他语言设计愿景或任何其他原因拒绝以这种方式实施的此功能。

@Igmat ,我认为我最初应该问一些事情

首先,我仍然不明白为什么您需要进行任何类型的更改才能发出,而且我认为任何基于类型的发出都是不可接受的。 在此处查看我们的非目标。

我应该提出的另一个问题是使用反向引用的正则表达式问题。 我的理解(和经验)是正则表达式中的反向引用可以强制测试按其输入的时间指数运行。 这是一个角落案例吗? 也许吧,但这是我一般希望避免的事情。 这一点尤其重要,因为在编辑器场景中,在一个位置进行类型检查应该花费最少的时间。

另一个问题是我们要么依赖于运行 TypeScript 编译器的引擎,要么构建一个自定义的正则表达式引擎来执行这些事情。 例如,TC39 正在移动以包含一个新的s标志,以便.可以匹配换行符。 ESXXXX 和支持此功能的旧运行时之间会存在差异。

@igmat - 在我看来,在运行时发出正则表达式毫无疑问会很有用。 但是,我认为他们不需要此功能有用(并且从@DanielRosenwasser所说的声音

但是我们还必须处理从字符串到正则表达式验证类型的缩小以使其有用

我认为只有当我们要将动态字符串缩小到正则表达式验证类型时才会出现这种情况。 这变得非常复杂。 即使在这个简单的情况下:

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

我们不能确定类型会匹配 - 如果数字是负数怎么办? 随着正则表达式变得越来越复杂,它只会变得越来越混乱。 如果我们真的想要这个,也许我们允许“类型插值: type Baz = /prefix:{number}/ ...但我不知道它是否值得去那里。

相反,如果我们只允许将字符串文字分配给正则表达式验证的类型,我们就可以实现目标。

考虑以下:

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'

你认为这是一个可行的选择吗?

@DanielRosenwasser ,我已经仔细阅读了设计目标,如果我理解正确,问题是违反了非目标#5。
但这在我看来并不是违规,而是语法改进。 例如,以前我们有:

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
}

实施此提案后,它将如下所示:

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
}

如您所见,代码几乎相同 - 这是正则表达式的常见简单用法。 但是第二种情况更具表现力,可以防止用户出现意外错误,例如在将字符串分配给要进行正则表达式验证的变量之前忘记检查字符串。
第二件事是,如果没有这种类型缩小,我们将无法在索引中正常使用正则表达式验证类型,因为在大多数情况下,此类索引字段与某些无法在运行时检查的变量一起使用,因为它可以使用文字完成.

@alexanderbird ,我不建议使此代码有效或在运行时和编译时添加一些隐藏检查。

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

由于我的建议,此代码必须抛出错误。 但是这个:

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

或这个:

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

将是正确的,甚至对发出的代码没有影响。

正如我在之前的评论中所展示的,即使对于常见用例,文字也绝对不够,因为我们经常不得不处理来自用户输入或其他来源的字符串。 如果不实现这种发射影响,用户将不得不以下一种方式使用这种类型:

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
}

或对于交叉点:

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
}

我不认为强迫用户复制代码并使用显式转换,因为编译器可以轻松处理它并不是一个好方法。 Emit 的影响确实非常小且可预测,我相信它不会让用户感到惊讶或导致某些功能被误解或难以定位错误,而在不发出更改的情况下实现此功能肯定会。

总之,我想简单地说, regex-validated类型既是作用域变量又是编译器类型。

@DanielRosenwasser@alexanderbird好的,我还有一个想法。 像这样的语法怎么样:

const type Email = /email-regex/;

在这种情况下,用户必须明确定义他/她希望它同时作为typeconst ,因此除非与此类修饰符一起使用,否则实际类型系统不会发出更改。 但是如果它与它一起使用,我们仍然能够通过添加与 for 相同的发射来避免很多错误、强制转换和代码重复:

const Email = /email-regex/;

这似乎比这个提案的改进更大,因为这可能允许这样的事情(示例来自Redux项目):

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

被转换为

export const type SOME_ACTION = 'SOME_ACTION';

我试图找到一些类似的建议,但没有成功。 如果这可能是一种解决方法,并且如果您喜欢这样的想法,我可以为它准备设计提案和测试。

@DanielRosenwasser ,关于你的第二个问题 - 我认为它永远不会发生,因为在我的建议中,编译器只为文字运行正则表达式,而且似乎没有人会做这样的事情:

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

无论如何,我们可以测试影响实时性能的文字应该多长时间,并创建一些启发式方法,如果我们在某些编辑器场景中遇到这种情况时无法检查它,则会警告用户,但我们会在他编译时检查它项目。 或者可能有其他一些解决方法。

关于第三个问题,我不知道,正确地理解了一切,但似乎正则表达式引擎应视选择targettsconfig ,如果他们有不同的实现。 需要更多的调查。

@DanielRosenwasser有什么想法吗? 😄 关于最初的提案和关于最后的提案。 可能我必须对第二个进行更详细的概述,是吗?

@Igmat您的提议将验证限制为仅对字符串类型有用。 你对@rylphs 的提议有何看法? 这将允许对所有原始类型进行更通用的验证:

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

然而,我怀疑将这种机制从原始类型扩展到非原始类型会太多。
有一点, @DanielRosenwasser提出的问题——关于不同的正则表达式引擎实现——将被放大:根据运行 Typescript 编译器的 Javascript 引擎,验证功能可能会以不同的方式工作。

@zspitz看起来很有希望,但在我看来,它可能会过多地影响编译器性能,因为函数不受任何规则的限制,它会迫使 TS 计算一些过于复杂的表达式,甚至依赖于一些不可用的资源在编译时。

@伊格马特

因为功能不受任何规则的限制

你有什么具体的例子吗? 也许可以将验证语法限制为 Typescript 的“安全”/编译时已知子集。

如何让用户定义的类型保护定义新类型?

// 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看起来不错,但是类型扩展呢?

它们是否绝对需要可扩展? 是否有需要它的 TypeScript 设计原则? 如果没有,我宁愿使用@disjukr建议的名义类型,也不愿什么都不提供,即使它们不可扩展。

我们需要一些非常有创意的东西来获得可扩展性恕我直言 - 我们无法确定一个任意类型保护(函数)是否是另一个任意类型保护的子集。

我们可以使用类型断言心态获得基本的“可扩展性”(我不是说这是“非常有创意”的东西 - 我是说这是一个权宜之计,直到有人想出一些非常有创意的东西):

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
}

即使它不健全,这也可能有用。 但正如我在开始时所说的,我们是否要求它是可扩展的,还是我们可以按照@disjukr 的建议实现它? (如果是后者,我建议我们以@disjukr的不可扩展方式实现它。)

有点跑题,回复
对于逗号分隔的列表,您必须使用^$锚点(在大多数情况下,当您想要验证某些字符串时,它是相关的)。 锚有助于避免重复,例如,正则表达式为/^((dog|cat|fish)(,|$))+$/

允许字符串类型为正则表达式/#[0-9]{6}/并允许将类型嵌套到正则表达式${TColor}

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

结果:

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

用例:有一个专门用于在 TypeScript typestyle编写“类型安全”CSS 样式的库。 上面提出的功能将有很大帮助,因为库必须公开要在运行时使用的方法,建议的字符串正则表达式类型将能够在编译时对代码进行类型检查,并为开发人员提供强大的智能感知。

@DanielRosenwasser @alexanderbird @Igmat :IMO 这个提议将改变 TypeScript 和 Web 开发的游戏规则。 目前是什么阻止了它的实施?

我同意可扩展性和类型发射不应该妨碍该功能的其余部分。 如果在这些方面没有明确的路径,请稍后实施。

我到达这里是因为我想要一个 UUID 类型而不是一个字符串,因此在这种情况下定义字符串的正则表达式会很棒+检查类型有效性的方法(Email.test 示例)也会有帮助。

@skbergam我正在尝试再次自己实现它。 但是 TS 项目真的很大,我也有工作,所以几乎没有进展(我只设法为这个新功能创建了测试)。 如果有人在扩展 TS 方面有更多经验,我们将不胜感激...

有趣的是,这有效地创建了一个名义类型,因为我们无法在任何两个不同的正则表达式之间建立任何子类型/可赋值关系

@RyanCavanaugh早些时候@maiermic评论

编辑:看起来这个问题可以在多项式时间内解决(请参阅正则表达式的包含问题)。

但这可能还不够好? 人们当然希望没有太多的正则表达式关系,但你永远不知道。

关于类型检查,如果我们不喜欢复制正则表达式,并且typeof一个常量不够好(例如 .d.ts 文件),TS 对valueof e感觉如何,它发出e的字面值仅当e是字面值,否则会出现错误(并发出类似undefined )?

@maxlk也是题外话,但我把你的正则表达式改进为不匹配其他有效输入的尾随逗号: /^((dog|cat|fish)(,(?=\b)|$))+$/与测试https://regex101.com/r/AuyP3g/1。 这对逗号后的单词字符使用正向前瞻,强制先行以 DRY 方式重新验证。

你好!
这是什么状态?
您会在不久的将来添加此功能吗? 在路线图中找不到任何关于此的信息。

@lgmat如何将语法限制为单行箭头函数,仅使用lib.d.ts可用的定义?

这些很棒的改进可用吗? 也许至少在 alpha 版本中?

正则表达式验证类型非常适合编写测试,验证硬编码输入会很棒。

+1。 我们的用例很常见,我们需要一个字符串日期格式,如“dd/mm/YYYY”。

尽管正如提议的那样,这将是一个非常酷的功能,但它缺乏潜力:

  • 输出总是一个刺痛(尽管编译时检查),没有办法得到一个结构化的对象
  • 语法仅限于正则表达式可以做的,他们不能做太多,正则表达式的问题是他们是正则的,他们的结果是一个列表,而不是一棵树,他们不能表达任意长嵌套级别
  • 解析器必须用打字稿语法来表达,这是有限的且不可扩展的

更好的方法是外包解析并发送到插件,就像在#21861 中提出的那样,通过更陡峭的学习曲线,上述所有方法都不是问题,但是嘿! 正则表达式检查可以在其之上实施,以便原始提案仍然有效,由更先进的机制提出

正如我所说,更通用的方法是为任何文字自定义语法提供程序:#21861

例子:

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如何将语法限制为单行箭头函数,仅使用lib.d.ts可用的定义?

@zspitz这会让很多人不高兴,正如他们所看到的,这是可能的,但对他们来说是禁止的,基本上是为了他们的安全。

这些很棒的改进可用吗? 也许至少在 alpha 版本中?

据我所知,这还需要一个提案。 @gtamas@AndrewEastwood

我也认为#11152 会影响到这一点。

@Igmat您的提议将验证限制为仅对字符串类型有用。 你对@rylphs 的提议有何看法? 这将允许对所有原始类型进行更通用的验证:

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

然而,我怀疑将这种机制从原始类型扩展到非原始类型会太多。

我看到的主要问题是安全问题,想象一下一些恶意代码,它会在检查类型时使用缓冲区来获取用户的内存。 我们将不得不围绕这个实施大量沙箱。 我宁愿看到两种不同的解决方案,一种用于字符串,一种用于数字。

RegExp 对某些扩展免疫,因为您可以恶意使用它的唯一方法是进行一些回溯表达式。 话虽如此,有些用户可能会无意中这样做,因此,应该有某种保护。 我认为最好的方法是使用计时器。

有一点, @DanielRosenwasser提出的问题——关于不同的正则表达式引擎实现——将被放大:根据运行 Typescript 编译器的 Javascript 引擎,验证功能可能会以不同的方式工作。

确实如此,这很糟糕,但我们可以通过指定我们的代码库所需的 regExp 的“现代”部分来解决它。 它会默认为正常(是 ES3 吗?)正则表达式,适用于每个节点。 以及启用新标志和后视断言的选项。

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

如果用户已禁用带有高级标志的标志。

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

如果没有被告知,TypeScript 不会评估高级 RegExp。 但我建议应该给出警告,解释正在发生的事情以及如何启用高级 RegExp 检查。

如果用户启用了带有高级标志的标志并且他的节点支持它。

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

如果用户启用了带有高级标志的标志并且他的节点支持它。

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

我认为这是一个合理的方法。
程序员团队通常拥有相同版本的 NodeJS,或者很容易升级,因为他们的所有代码库都适用于拥有更新版本的人。
Solo 程序员可以随时轻松适应,

这个问题的现状如何? 看到 TypeScript 有如此巨大的潜力和几十个很棒的提案,真的很可惜,但它们并没有得到开发人员的太多关注……

AFAIK最初的提案很好,除了Emit 概述

它试图解决的问题可以通过引入正则表达式文字(这应该不难,因为它们实际上等效于字符串和数字文字)和类型运算符patternof (类似于typeofkeyof ),它将采用正则表达式文字类型并返回 _validated string_ 类型。 这是它的用法:

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在处理初始提案时,我没有考虑过使用附加类型运算符的这种解决方案。

我喜欢这种消除由类型引起的发射影响的方法,尽管这似乎更冗长。

这让我想到了如何扩展此提案,以便既跳过添加新关键字(如您所建议的那样)- IMO 我们已经拥有相当多的关键字,并且不会受到类型系统的影响(如我的提案中所示)。

这将需要 4 个步骤:

  1. 添加regexp-validated string literal类型:
    TypeScript type Email = /some-long-email-regex/;
  2. 让我们将核心库中的RegExp接口更改为通用接口:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. 在实际代码中更改正则表达式文字的类型推断:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. 使用conditional types功能添加类型助手,如InstanceType
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

用法示例:

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

@伊格马特酷。 您的建议对于 TypeScript 来说更自然,并且需要对编译器进行更少的更改,这可能是一件好事。 我的提议的唯一优点是正则表达式文字与字符串和数字文字感觉相同,这可能会让某些人感到困惑:

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

但我认为你的提议的简单性超过了一个缺点。

编辑:我真的不喜欢ValidatedStringType<R>的长度。 如果我们决定调用验证字符串模式,我们毕竟可以使用PatternOf<R> 。 我并不是说你的类型需要更长的时间来输入,大多数人只会输入前三个字母并点击 Tab。 它只是具有更大的代码分页影响。

@Igmat从开发的角度来看,您的解决方案非常出色,但随着可读性的提高,最好能像@m93a提出的那样采用这种可能性。 我认为它可以以相同的方式在内部表示,但它应该尽可能简单地呈现给用户。

@Akxe我不认为开发人员会喜欢添加另一个只有一个非常具体的用例的关键字。

@RyanCavanaugh你能告诉我们你对此的看法吗? (特别是原提案和最后四条评论(不包括这一条)。)谢谢! :+1:

字符串的通用参数如何,默认为.*

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

字符串文字'foo'可以被认为是string<foo>的糖

我真的不喜欢ValidatedStringType<R>的长度。 如果我们决定将验证字符串称为 _patterns_,我们毕竟可以使用PatternOf<R>

@m93a ,IMO,在这种情况下,最好将它们称为PatternType<R>以与现有的InstanceTypeReturnType助手保持一致。

@amir-arad,有趣。 在这种情况下, interface RegExp会是什么样子?

@RyanCavanaugh如果有帮助,我可以用新发现的方式重写原始提案。 我是不是该?

@amir-arad 您提议的语法与 TypeScript 的其余部分相冲突。 现在您只能将类型作为泛型参数传递,而不是任意表达式。 您提议的语法将非常令人困惑。

将泛型类型视为接受类型并返回类型的函数。 以下两段代码在含义和语法上都非常接近:

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

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

你的新语法就像提议 JavaScript 中的正则表达式应该写成let all = String(.*) ,这将是对函数调用语法的丑陋滥用。 因此,我认为你的提议没有多大意义。

@m93a我的建议是类型注释,而不是 javascript。

@Igmat从我的头顶,怎么样:

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

@amir-arad,抱歉,我无法为您的建议添加更多有价值的细节,但乍一看,整个 TS 编译器似乎发生了非常重大的变化,因为string是非常基本的原语。

尽管我没有看到任何明显的问题,但我认为这样的提案应该更加详细,涵盖许多现有的场景,并适当说明其目的。
你的提议增加了一种类型并改变了一种原始类型,而我的只增加了一种类型。

不幸的是,我还没有准备好花大量时间为此类功能创建提案(此外,您可能会注意到并非 TS 中的每个提案都没有明显延迟地实施),但如果您愿意为此工作,我'如果需要,我很乐意为您提供我的反馈。

如果这些正则表达式类型是真正的正则表达式(不是非正则的类似 Perl 的正则表达式),我们可以将它们转换为确定性 FSM,并在这些表达式上使用笛卡尔积构造来获得所有的连接和分离。 正则表达式在布尔运算下是封闭的。

此外,如果字符串文字类型不是原子的,而是表示为编译时字符列表,那么它将允许实现库中的所有运算符。 那只会稍微降低性能。

编辑:修正一个错误。

顺便指出 Mithril真的可以使用这些,如果没有它,在一般情况下类型安全几乎是不可能的。 超脚本和 JSX 语法都是这种情况。 (我们都支持。)

  • 我们的生命周期钩子oninitoncreateonbeforeupdateonupdateonbeforeremoveonremove都有自己的特殊原型。
  • DOM vnode 上的事件处理程序实际上是其他任何以on开头的东西,我们同时支持事件侦听器函数和事件侦听器对象(使用handleEvent方法),与addEventListenerremoveEventListener
  • 我们酌情支持键和参考。
  • 其他一切都被视为属性或属性,具体取决于它们在支持 DOM 节点本身上的存在。

因此,使用正则表达式验证的字符串类型 +类型否定,我们可以对 DOM vnode 执行以下操作:

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

(能够从这样的正则表达式中提取组也很好,但我不会屏住呼吸。)

编辑:澄清提案中的一些关键细节。
编辑 2:更正技术位,使其在数学上实际上是准确的。
编辑 3:添加对单字符联合的通用主演的支持

这是一个尝试更可行地解决这个问题的具体建议:模板文字类型。

另外,我觉得完整的正则表达式可能不是一个好主意,因为它应该很容易与其他类型合并。 也许这可能更好:模板文字类型。

  • `value` - 这实际上相当于"value"
  • `value${"a" | "b"}` - 这实际上相当于"valuea" | "valueb"
  • `value${string}` - 这在功能上等同于正则表达式/^value/ ,但"value""valuea""valueakjsfbf aflksfief fskdf d"都可以分配给它。
  • `foo${string}bar` - 这在功能上等同于正则表达式/^foo.*bar$/ ,但更容易规范化。
  • 当然,可以有多个插值。 `foo${string}bar${string}baz`是有效的模板文字类型。
  • 插值必须扩展string ,并且不能是递归的。 (第二个条件是出于技术原因。)
  • 模板文字类型A可分配给模板文字类型B当且仅当可分配给A的字符串集是可分配给B的字符串集的子集

除上述之外,还存在特殊的starof T类型,其中T必须仅由单字符字符串文字类型组成。 string将作为starof (...)的类型别名存在,其中...是从 U+0000 到 U+FFFF 的所有单个 UCS-2 字符串文字的并集,包括 lone代理人。 这使您可以为 ES base-10 数字文字定义完整的语法,例如:

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

同样,可以调整某些内置方法以返回此类类型:

  • Number.prototype.toString(base?) - 这可以为静态已知的base返回上述Numeric类型或它的某些变体。
  • +xx | 0parseInt(x)和类似的 - 当x已知Numeric如上定义的

最后,您可以像这样提取匹配的组: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never 。 模板提取假定它始终使用全名,因此您必须明确使用${string}插值来搜索任意包含。 这是非贪婪的,所以 ` "foo.bar.baz" extends ${infer T}.${infer U} ? [T, U] : never返回["foo", "bar.baz"] ,而不是["foo.bar", "baz"]


从技术角度来看,这比原始正则表达式更容易实现。 JS正则表达式甚至没有正规的-他们成为上下文敏感与反向引用,他们渴望参与作为的形式有很多复杂的,你阻止递归这些,模板文字类型生成一个正规语言的每个,一个与基础理论非常一致(但仅支持其中的一个子集)。

  • 空语言: ""
  • 工会: "a" | "b"
  • 串联: `${a}${b}`
  • Kleene 星(部分): starof TT只能包含单个字符和联合。)

可能会使字符串子类型检查子图同构问题的子集

  1. 到目前为止,常见的情况是小的有限字符串的并集,您可以用树来建模。 这是比较明显的工作。 (我不建议尝试将它们连接为绳索,因为这会使上述匹配算法复杂化,但是将单字符联合和类似的归一化为单个拆分 + 连接是完全没问题的。)

  2. 您可以将整个统一类型建模为有向图,其中:

    1. 这些字符的带星号的联合是子图,其中父节点具有到子图的每个字符和每个子节点的边,并且每个字符都有到子图的所有其他字符和所有子节点的边。
    2. 图的其余部分包含一个有向树状结构,代表所有其他可能性。

    根据这个 Math.SE 聊天我简要地(大约从这里开始),我发现这个结果图将有一个有界属(即在其他边上跳跃次数有限*),并且没有任何starof类型,有界度。 这意味着类型相等将其简化为多项式时间问题,并假设您对联合进行规范化,它也不是超级慢,因为它只比树相等快一些。 我强烈怀疑整个提案(子图同构问题的一个子集)的一般情况也是具有合理系数的多项式时间。 (上面链接的维基百科文章在“算法”和可能适用特殊大小写的参考部分中有一些示例。)


  3. 这些键都不可能很大,所以这里的大部分实际运行时成本在实践中被其他东西摊销了。 只要对小键快,就足够了。


  4. 将被比较的所有子图至少共享一个节点:根节点。 (这代表字符串的开始。)因此,这将大大减少其自身的问题空间并保证多项式时间检查。


当然,这些类型之间的交集是不平凡的,但我觉得仅仅由于上述限制,就存在类似的赎回因素。 特别是,最后一个限制使得它显然是多项式时间做的。

* 在数学上,genus 的定义对我们程序员来说有点违反直觉(在没有任何跳跃的情况下绘制图形所需的最少孔数),但有界 genus(有限数量的孔洞)意味着有限的跳跃次数.

使用这个具体的建议,下面是我从这条评论中得到的例子的翻译:

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

编辑:这也可以使用其属性简写方式正确键入 90% 的 Lodash 的_.get方法和相关方法,例如它的_.property(path)方法和它的_.map(coll, path)简写。 可能还有其他几个我没有想到的,但这可能是我能想到的最大的一个。 (我将把该类型的实现留给读者作为练习,但我可以向你保证,将它与带有立即索引记录的条件类型的常用技巧相结合是可能的,例如{0: ..., 1: ...}[Path extends "" ? 0 : 1] ,处理静态路径字符串。)

我的建议是我们将精力集中在实现类型提供程序上,它可用于实现正则表达式类型。

为什么使用类型提供程序而不是直接实现正则表达式类型? 因为

  1. 这是一个更通用的解决方案,它为 TypeScript 增加了许多新的可能性,使其更容易从更广泛的开发人员那里获得支持,而不是那些看到正则表达式字符串类型中的价值的人。
  2. 打字稿回购所有者似乎对这个想法持开放态度,并且正在等待正确的提议。 见#3136

F# 有一个开源正则表达式类型提供程序。

关于类型提供程序的一些信息: https :

可以想象,一旦类型提供程序被实现并且正则表达式类型提供程序被实现为一个开源库,人们会像这样使用它:

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我不相信这是正确的方法,至少对于这个请求不是。

  • TypeScript 是结构类型的,而不是名义类型的,对于字符串文字操作,我想保留这种结构精神。 像这样的类型提供者会创建一个名义上的string子类型,其中RegexProvider</^foo$/>不会被视为等同于"foo" ,而是它的名义子类型。 此外, RegexProvider</^foo$/>RegexProvider</^fo{2}$/>将被视为两种不同的类型,我不喜欢这种类型。 相反,我的建议直接与字符串的核心集成,直接由形式语言识别理论提供信息,以确保它自然适应。
  • 使用我的,您不仅可以连接字符串,还可以通过Key extends `on${infer K}` ? K : never甚至Key extends `${Prefix}${infer Rest}` ? Rest : never提取部分字符串。 类型供应商提供此功能,而且也没有明确的方法应该如何,如果是要添加的功能。
  • 我的在概念层面上要简单得多:我只是建议我们添加字符串连接类型,并且对于条件类型的 RHS,能够提取其逆。 我还建议它与string本身集成以代替正则表达式/.*/ 。 它不需要 API 更改,除了两个理论上复杂的部分(大部分与代码库的其余部分分离)之外,计算模板文字类型是否可分配给另一个并从字符串中提取切片,如果不是更简单的话,是相似的,实施。

顺便说一句,我的建议仍然可以输入PhoneNumber示例。 它有点冗长,但我正在尝试对已经在 TS 领域中的数据进行建模,而不是其他地方存在的数据(F# 的类型提供程序最有用的是什么)。 (值得注意的是,这在技术上会扩展到此处可能的电话号码的完整列表。)

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$/> 和 RegexProvider^fo{2}$/> 将被视为两种不同的类型

类型提供者可能需要实现某些equalscompare方法,以便正则表达式类型提供者的类型提供者作者可以定义上述两种情况是等效类型。 类型提供者作者可以根据需要实现结构化或名义类型化。

也许也可以将您的字符串文字类型实现为类型提供程序。 我不认为语法可能是相同的,但是您可以接近接受可变数量参数的类型提供程序。

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但是类型"123-456-7890"分配给您的类型吗? (如果是的话,那将复杂实施和减缓检查了很多。)

与手头的讨论半相关,如果类型不是固定长度的(如电话号码)怎么办? 我最近希望使用它的一种情况是存储格式thread_{number}的房间名称。

匹配这样一个值的正则表达式是thread_[1-9]\d* 。 根据提议的内容,匹配这种格式似乎不可行(甚至不可能)。 在这种情况下,值的数字部分可能是大于零的 _any_ 长度。

@jhpratt我修改了我的提案以适应这一点,形式为starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ ,因为它只需要对其进行一些小的改动。 它的优化方式与string优化为/^[\u0000-\uFFFF]*$/ ,所以我决定继续进行概括。

由于计算复杂性问题,我不想进一步扩展starof ,比如接受任意非递归联合:验证两个任意正则表达式*是否等价可以在多项式空间多项式时间(将两者都转换为最小的 DFA 并进行比较 - 通常的方式,但在实践中非常慢),但两种方式在实践中都很慢,而且 AFAICT 你不能同时拥有它。 添加对平方的支持(如a{2} ),它基本上是不可行的(指数复杂度) 。 这只是为了等价,检查一个 regexp 是否匹配另一个 regexp 匹配的字符串的子集,这是检查可分配性所需的,显然会更加复杂。

* 数学意义上的正则表达式:我只包括单个字符, ()(ab)(a|b)(a*) ,其中ab是(可能不同的)这个列表的每个成员。

这可能是一个愚蠢的问题,但是......为什么如果有足够的限制,支持验证函数(lambda 或命名)不是很容易?

例如,假设我们使用“:”来表示下一个元素是一个验证器(如果您对此有意见,请用您想要的任何内容替换“:”):

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

作为最初的开始,打字稿只能接受以下验证函数:

  • 有一个参数,需要/假定为“基本”类型
  • 仅引用验证器函数中定义的变量
  • 返回一个值(在验证过程中将被强制为 bool)

上述条件对于打字稿编译器来说似乎很容易验证,一旦假设这些条件,大部分实现复杂性就会消失。

此外,如有必要将初始范围限制为可管理的大小:

  • 验证函数只能添加到本机类型(字符串、数字)的子集

我不认为最后一个限制是必要的,但是如果对它是否有任何疑问,我也不认为值得花太多时间讨论它,因为具有上述限制的解决方案仍然可以解决大量的实际用例。 此外,我认为上述限制几乎没有缺点,因为稍后放宽它们将是一个简单而自然的扩展,不需要更改基本语法,只会扩展编译器对语言的支持范围。

@mewalig这意味着看起来像运行时函数的东西实际上不会在运行时执行,而是在编译时执行(以及每次您想要检查可分配性时)。 这些函数无法从运行时访问任何东西(变量、函数),这会让人感觉很尴尬。

另外,您通常不希望编译器运行您抛出的任何内容,尤其是优化不当的函数或完全恶意的while(true){} 。 如果你想要元编程,你必须巧妙地设计它。 只是随机允许运行时代码在编译时运行将是“PHP 方式”。

最后,您建议的语法切换了通常的模式

let runtime: types = runtime;

(即冒号后的类型)由内而外,实际上是

type types = types: runtime;

这太可怕了。 所以谢谢你的提议,但这绝对是个坏主意。

这些函数无法从运行时访问任何东西(变量、函数),这会让人感觉很尴尬。

当然他们可以,如果编译器有一个可用的 ECMAScript 运行时( tsc可以,顺便说一句!)。 您显然在编译时语义方面存在歧义问题,例如fetch()与运行时语义,但这就是迭代的意义所在。

只是随机允许运行时代码在编译时运行将是“PHP 方式”。

它与C++ constexpr函数非常相似,都很好。 那里的解决方案是说constexpr只能使用constexpr ,但一切都可以使用constexpr 。 然后你可以有constexpr等效版本的编译时文件系统的文件系统,它可能非常强大。

语法对我来说也大致不错:LHS 是一种类型,当然 RHS 也是某种类型。 我的问题更多是关于如何组合超出“基本”类型的类型,但这也是可以解决的。

所以谢谢你的提议,但这绝对是个坏主意。

这最终可能是一个坏主意,但现在我只是看到一个非常不明确的想法,可能需要偏离打字稿的目标太远。 这并不意味着可能没有与之相似的好主意!

关于这个特性的讨论现在似乎停止了( PR已经关闭,根据设计说明团队_不想承诺这个,直到我们有名义类型和通用索引签名,我们应该知道那些看起来像什么。_)。

无论如何,我想对当前的 PR 提出另一个假设的扩展来支持正则表达式模式提取( @isiahmeadows提出了他自己的提议,但说实话我现在无法理解它......)。

我真的很喜欢当前的PR ,并将以此为基础提出我的建议。 我想提出基于泛型类型参数推断的语法,我们对函数(以及带有infer关键字的条件类型)具有这种推断。 仅仅因为人们已经有了一些直觉,即在泛型函数中您可以从传递的文字对象中“提取”类型。

例如我们有这种类型。

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

我们可以使用这种类型来测试文字类型

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

但是,当我们在函数参数中使用 Regex 类型时,我们可以使用尖括号语法来表示我们要推断匹配的字符串。 例如

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

注意res1推断类型是["foo", "bar"]

有什么用吗?

  1. Ember.js/lodash 获取函数

您可以实现类型安全的“字符串路径”getter,以便此代码可以工作:

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

但是,如果我们想为固定的最大可能get的“深度”数量避免许多重载,可能需要解决这个问题

  1. 在映射类型中使用提取的参数。

例如,如果我们能够做这样的事情https://github.com/Microsoft/TypeScript/issues/12754。 然后我们就有可能反转函数(从给定类型的所有属性中去除一些前缀/后缀)。 这个可能需要引入一些更通用的映射类型语法形式来为属性选择新的键(例如像{ [ StripAsyncSuffix<P> for P in K ] : T[P] }这样的语法,有人已经提出了类似的东西)

可能还会有其他用例。 但我想大多数都适合这两种类型(1.根据提供的字符串文字找出正确的类型,2.将输入类型的属性名称转换为新定义类型的新属性名称)

这是我们可以做的事情。

我目前正在构建自定义 lint 规则以便能够验证 url - 不过,如果我们可以定义可选参数,这会容易得多 - 这需要一个正则表达式才能验证我们的 id

一般来说,这将为我们提供更多的权力来断言整个代码库中 props 的有效性

类型提供程序、模板字符串文字或其他建议是否有任何变动? 这将是一个非常棒的工具。

我给这家目前的解决方法是使用像一个标记接口

interface TickerSymbol extends String {}

唯一的问题是,当我想将它用作索引键时,我必须将它转换为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

但是,JavaScript 似乎可以使用String索引类型(大写 S)。

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

@DanielRosenwasser我在这里有一个提案,并且在 2016 年底创建了一个单独的提案,所以可以更新这个标签吗?

我们已经审查了上述提案,并提出了一些问题和意见。

迄今为止提案的问题方面

类型创建发射

我们致力于保持类型系统被完全擦除,因此需要类型别名来生成发出的代码的建议超出了范围。 我将在这个线程中强调一些例子,这些例子可能以不明显的方式发生:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -220180091 - 同时创建一个函数和一个类型

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - 也这样做

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

我会重申:这是一个非启动器。 TypeScript 中的类型是可组合的,在这个世界上不可能从类型中发出 JS。 迄今为止最长的提案具有广泛的排放类型; 这是行不通的。 例如,这将需要大量的类型定向发射:

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

禁止交叉路口

实际上,普通类型和正则表达式验证的类型确实不同,因此我们需要规则如何正确处理它们的并集和交集。

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

TypeScript 不会在交叉点的实例化上出错,因此这不会成为任何最终设计的一部分。

人体工学

总的来说,我们最重要的一点是,我们希望您不会两次编写相同的 RegExp (一次在值空间中,一次在类型空间中)。

鉴于上述对类型发出的担忧,最现实的解决方案是在值空间中编写表达式:

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

当然,您仍然可以在类型空间中编写 RegExp,但是没有可用的运行时验证,任何非文字使用都需要重新测试或断言:

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

用例的收集和澄清

对于一种新的类型,我们希望看到几个示例,其中:

  • 正在解决的问题没有更好的替代方案(包括语言中尚未出现的合理替代方案)
  • 该问题在实际代码库中以有意义的频率发生
  • 建议的解决方案很好地解决了这个问题

文字的编译时验证

这个线程意味着各种各样的用例; 具体的例子已经比较少见了。 令人不安的是,其中许多示例似乎并不完整——它们使用的 RegExp 会拒绝有效输入。

  • 字体颜色- AFAIK 任何接受十六进制颜色的东西也接受例如“白色”或“天蓝色”。 这也错误地拒绝了rgb(255, 0, 0)语法。
  • SSN、邮政编码等- 好的,但为什么您的代码中有文字 SSN 或邮政编码? 这实际上是否需要名义类型? 如果您有一个不能被 RegExp 准确描述的字符串子类,会发生什么? 参见“竞争提案”

    • 整数- 错误地拒绝"3e5"

    • 电子邮件——这通常被认为是一个坏主意。 不过,您的代码中有电子邮件地址字符串文字吗?

    • CSS Border 规范- 我相信一个独立的库可以提供一个准确的 RegEx 来描述它本身支持的 DSL

    • 编写测试- 这是硬编码输入有意义的地方,尽管这几乎是对立的,因为您的测试代码可能应该提供大量无效输入

    • 日期格式- 如何/为什么? Date有一个构造函数; 如果输入来自运行时之外,那么您只需要一个名义类型

    • URI - 你可以想象fetch会指定hosthttp(s?):

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

一个问题是“精确性”——当有人帮助出现DefinitelyTyped 并将RegExp 类型添加到库中的每个函数时会发生什么,从而破坏每个非文字调用? 更糟糕的是,定义文件的作者必须与它的使用者完全同意验证 RegExp 的“正确拼写”是什么。
似乎这很快让我们走上了通向巴别塔的道路,每个图书馆都有自己的 URL 版本,主机名,电子邮件等,以及连接两个图书馆的任何人必须插入类型断言或复制正则表达式以满足编译器。

执行运行时检查

有一些关于检查的讨论,我们希望确保函数的参数已被先前的正则表达式验证,例如早期的人体工程学部分中的fn如果需要测试的 RegEx 是众所周知的,这看起来很简单而且很有价值。 然而,这是一个很大的“如果”——在我的记忆中,我不记得有一个提供验证正则表达式的库。 它可能提供验证功能- 但这意味着要提供的功能是名义或标记类型,而不是正则表达式类型。

欢迎对此评估提供反证。

属性键/正则表达式字符串索引器

一些库根据属性名称处理对象。 例如,在 React 中,我们希望将类型应用于名称以aria-开头的任何 prop:

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

这实际上是一个正交概念(我们可以添加 Regex 类型而不添加 Regex 属性键,反之亦然)。

TODO(我或任何人):为此打开一个单独的问题。

竞争性提案

名义或标记类型

假设我们有某种名义/标记类型:

type ZipCode = make_unique_type string;

然后你可以写一个函数

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

在这一点上,您真的需要 RegExp 类型吗? 有关更多想法,请参阅“编译时”检查部分。

相反,假设我们有 RegExp 类型而不是名义类型。 在非验证场景中开始(ab)使用它们变得非常诱人

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

线程中的一个常见问题是这些正则表达式将有助于验证测试代码,因为即使在生产场景中代码将针对运行时提供的字符串而不是硬编码文字运行,您仍然需要一些验证,以确保您的测试字符串是“正确的”。 但是,这似乎是名义/标记/品牌字符串的参数,因为您将以任何一种方式编写验证函数,并且测试的好处是您知道它们可以详尽地运行(因此测试输入中的任何错误都会在开发周期的早期标记)。

非问题

我们讨论了以下方面并认为它们不是阻碍因素

主机能力

较新的运行时比旧的运行时支持更多的 RegExp 语法。 根据 TypeScript 编译器运行的位置,根据运行时解析新 RegExp 功能的能力,某些代码可能有效或无效。 在实践中,大多数新的 RegExp 功能都相当深奥或与组匹配有关,这似乎与此处的大多数用例不一致。

表现

RegEx 可以做无限量的工作,而匹配一个大字符串可以做任意大量的工作。 用户已经可以通过其他方式自己 DOS 了,不太可能恶意编写低效的 RegExp。

子类型( /\d*/ -> /.*/ ?)、联合、交集和不适宜居住

理论上/\d+//.+/一个已知子类型。 假设存在算法来确定一个 RegExp 是否匹配另一个 RegExp 的纯子集(在某些约束下),但显然需要解析表达式。 在实践中,我们 100% 同意 RegExpes 不会根据它们匹配的内容形成隐式子类型关系; 这可能更可取。

只要正确定义了可分配关系,并集和交集操作就可以“开箱即用”。

在 TypeScript 中,当两个原始类型在交叉点“碰撞”时,它们会减少到never 。 当两个 RegExp 相交时,我们只是将其保留为/a/ & /b/而不是尝试生成一个新的 RegExp 匹配两个表达式的交集。 never不会有任何减少,我们需要一种算法来证明没有字符串可以满足双方(这是与前面描述的问题并行的问题:子类型)。

下一步

总而言之,接下来的步骤是:

  • 为正则表达式命名的属性键 AKA 正则表达式字符串索引器提交一个单独的问题
  • 获取用于字符串文字编译时验证的具体且合理的用例

    • 示例:确定绝对类型或其他库中将从中受益的函数

  • 了解名义/标记/品牌类型是否是用于非文字验证的更灵活和更广泛适用的解决方案
  • 识别已经提供验证 RegEx 的库

用例:Hyperscript (https://github.com/hyperhype/hyperscript) 之类的函数
超脚本函数通常被称为h('div#some-id')
正则表达式模式匹配器将允许确定h的返回类型,在示例案例中将是 HTMLDivElement。

如果类型系统能够添加字符串文字,那么基本上任何 CSS 属性都可以是类型安全的

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

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

CSS 选择器也可以被验证( element.class#id - 有效, div#.name - 无效)

如果捕获组会起作用(以某种方式),那么 Lodash 的get方法可能是类型安全的

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

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

这也可能是一件事:

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

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

用例:Hyperscript(hyperhype/hyperscript)之类的函数

那个正则表达式会是什么样子,或者它会提供什么验证? 这是用于基于正则表达式的函数重载吗?

FWIW 该库接受命名空间标签名称,也可以在任意标签名称上运行

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

它还接受类和 id 值的无限混合

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

CSS 选择器也可以被验证

CSS 选择器无法通过正则表达式进行验证

那个正则表达式会是什么样子,或者它会提供什么验证? 这是用于基于正则表达式的函数重载吗?

不是 OP,但我认为,是的,类似于HTMLDocument#createElement()重载,例如:

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

我确定 RE 是不完整的。 请注意,这是验证 CSS 选择器的一种特殊情况,它以常规方式在许多上下文中使用。 例如,如果您使用复杂的选择器,则HTMLDocument.querySelector()返回HTMLElement作为回退是完全可以的。

不过,我很好奇是否有既可行又有用的非重载示例。

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

我的用例是我在 CCXT 库中的此评论中解释的用例,其中我有表示TickerSymbol的字符串。 我真的不在乎是否检查了正则表达式模式,但我希望它们被视为string子类型,所以我得到更严格的赋值、参数类型检查等。我发现它在我进行函数式编程时非常有用,我可以在编译时轻松跟踪 TickerSymbols、货币、资产等,而在运行时它们只是普通字符串。

@omidkrad这听起来像是您需要名义类型,而不是正则表达式验证类型。

@m93a在我的情况下,我可以使用名义类型,但对于相同的用例,您可以使用正则表达式验证的类型进行更严格的类型检查和自我记录字符串类型。

CSS 选择器也可以被验证

CSS 选择器无法通过正则表达式进行验证

好吧,如果正则表达式能让我们将它们拼接在一起,我们就可以复制 CSS 正则表达式……,对吗?

(草稿)CSS 类型对象模型

https://drafts.c​​ss-houdini.org/css-typed-om/

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

潜在地减轻了使用字符串类型 CSS 模型的愿望。

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

@RyanCavanaugh特别是对于 Mithril,标签名称是通过^([^#\.\[\]]+)的捕获组提取的(默认为"div" ),但匹配^(${htmlTagNames.join("|")})就足以满足我们的目的。 因此,使用我的提案,这足以满足我的目的:

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

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

至于事件和属性,我们可以切换到这个曾经否定的类型土地:

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

顺便说一句,这种无缝集成和避免复杂性是我仍然喜欢我的建议而不是文字正则表达式的原因。


不过,我知道没有办法用纯正则表达式类型来做到这一点。 我确实想指出这一点。

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

根据作为描述预期响应类型的字符串给出的内容,弯曲具有不同的返回类型,例如

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

它还接受一些其他参数,例如作为字符串的 method 和 url,但这些可以出现在任何位置,因此如果我们尝试使用联合来描述所有返回类型( 'json' | 'buffer' | 'string' ),这反而会变得愚蠢当与联合中的 url 和方法类型结合时,仅为string ,这意味着我们无法根据第一次调用中给出的类型自动推断返回类型。

@Ovyerus正则表达式类型如何帮助您? 你希望写什么? 您可以使用重载或条件类型对类似于 Bent 的行为进行建模。

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 + liErQgwCgkg2ugMWER0EY0QA7tFyFShogZsp​​DAotjyABbAYBgVBmAD0Eqk0XYYApADoSOx + Q0 + GCBVsgA

哦,我不清楚抱歉,我相信我的问题更多是在字符串的开头匹配http(s):以检测基本 URL。

本特的签名更像是

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

然而,将BaseUrl作为字符串吸收了 HttpMethods 并返回类型联合,最终只是string 。 将它作为字符串也“不正确”匹配弯曲的工作方式,因为它确实检查^http:^https:存在,以确定它应该用作基本 url 的内容。

如果我们有正则表达式类型,我可以将 BaseUrl 定义为type BaseUrl = /^https?:/ ,理想情况下,这将正确验证不是 HTTP 方法或响应编码的字符串,并且不会将它们吸收到string类型。

没错,我也一样。

——
普罗科普·西梅克

2019 年 10 月 20 日 03:23:30,迈克尔·米切尔 ([email protected])
写道:

哦,我不清楚抱歉我相信我的问题更像是
匹配 http(s):在字符串的开头以检测基本 URL。

本特的签名更像是

类型 HttpMethods = 'GET' | '补丁' | ...type StatusCode = number;type BaseUrl = string; // 这是我理想情况下需要查看字符串是否匹配 http(s):type Headers = { [x: string]: any; 的地方 };
类型选项 = HttpMethods | 状态码 | 基本网址 | 标题;
函数弯曲(...参数:选项[]):请求函数函数弯曲(...参数:(选项| 'json')[]):RequestFunction// 等等

但是将 BaseUrl 作为字符串吸收 HttpMethods 并返回
类型联合,最终只是字符串。 把它当作一个字符串
也“不正确地”匹配弯曲的工作方式,因为它确实检查存在
^http: 或 ^https: 以确定它应该用作基础
网址。

如果我们有正则表达式类型,我可以将 BaseUrl 定义为 BaseUrl = /^https?:/,
理想情况下,这将正确验证不是 HTTP 方法的字符串或
响应编码,以及不将它们吸收到字符串类型中。


您收到此消息是因为您订阅了此线程。
直接回复本邮件,在GitHub上查看
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXWJ2201000000000000000000000000010001001
或取消订阅
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

我对用例的想法是检测函数的参数类型。

基本上我有一个表示标识符的字符串的定义明确的正则表达式格式。 我可以使用装饰器,但是扩展字符串类型可以让我使用一个类型来表示传递给函数的标识符。

重申一下,我们需要您想以类型化方式编写的 JavaScript 代码示例 - 否则我们只能猜测您要建模的内容(以及是否已经有建模方法)。

@DanielRosenwasser下面是我们想要强制输入的代码示例。 http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + JU6 +华++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt好像你想要一个名义类型,而不是一个 RegExp 类型? 您不希望调用者出现这样的随机站点验证调用:

// 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 您能够用正则表达式描述 UUID 的事实是字符串本身格式的产物,而您试图表达的是 UUID 是一种特殊类型,其支持格式恰好是字符串.

所以 3.7 的Assertion Functionsnominal Feature 的组合可以做到这一点(?)

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

  }
}

这也会失败吗?

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

想了一会儿,我发现我提出的解决方案有问题🤔
asserts只在运行时触发错误 -> 👎
Regex-Validation可能会触发编译时错误 -> 👍
否则这个提议没有意义

编辑:
另一个问题: someFunc(uuid: any): asserts uuid is UUID不返回 UUID,它抛出或返回is UUID -> true
所以我不能使用这个函数以这种方式将 UUID 分配给mainUser

@RyanCavanaugh我们希望为 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)

我们想静态拒绝这些:

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

理想情况下,我们也想静态拒绝这些,但它不是那么高的优先级,没有它们我们也可以生存:

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

这将需要更复杂的类型定义,我们需要自定义类型检查失败消息来帮助用户找出类型检查失败的原因。

其他超脚本库和基于超脚本的框架(如 react-hyperscript)也有类似的担忧。

希望这可以帮助!

@isiahmeadows更好的方式让您使用某种形式的选择器字符串构建器,它将返回带有正确类型的品牌字符串。 喜欢:

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

@ anion155还有其他方法可以到达那里,但这是关于键入一个库,其 API 是由其原作者在 2014 年设计的。如果我现在设计它的 API,我可能会使用m("div", {...attrs}, ...children)和没有任何超标糖(更容易输入,更容易处理),但现在做很多事情已经太晚了。

我有很多的说。 然而,我很不耐烦。 所以,我会一次一点地释放我的想法。

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

关于“精确性”(伙计,我喜欢这个词),
我不认为我们应该担心太多。

类型系统已经是图灵完备的。
这基本上意味着我们可以对很多事情
(比如,建模所有的SQL?无耻的插件=P)

但是你没有看到(太多)人全力以赴,并以疯狂的方式使用所有类型运算符来阻止库彼此兼容。 我喜欢认为图书馆作者往往足够冷静......对吗?

我并不经常希望字符串模式类型/正则表达式验证的字符串类型,但它们肯定有助于提高我的代码库的类型安全性。


用例

在我的头顶上,我可以想到一个最近的例子。 (还有一堆,但我是一个健忘的人)

当与 Stripe 的 API(一个支付处理平台)集成时,他们使用ch_charge相关的标识符,使用re_refund相关的标识符等。

PatternOf</^ch_.+/>PatternOf</^re_.+/>对它们进行编码会很好。

这样,当打错字时,

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

我会得到一个错误,

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

尽管我喜欢名义/标记类型,但它们更不符合人体工程学且容易出错。
我总是将名义/标记类型视为最后的手段,因为这意味着有些东西是 TS 类型系统无法建模的。

此外,标记类型非常适合幻像类型。
名义类型基本上没有用处。
(好吧,我可能有偏见。它们之所以有用只是因为unique symbol但我喜欢认为我并没有完全错。)

用于验证的“ValueObject”模式甚至更糟,我不会费心谈论它。


比较

下面我来对比一下,

  • 字符串模式类型/正则表达式验证的字符串类型
  • 标称类型
  • 结构标签类型

我们都同意“ValueObject”模式是最糟糕的解决方案,在比较中不用理会它,对吧?


字符串模式类型

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

看那个。

  • 非常适合字符串文字。
  • 对于string非文字还不错。

标称类型...

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

看那个。

  • 对于字符串文字来说太糟糕了。
  • 克服了字符串文字障碍后,还不错……对吧?

但是这个提议的主要用例是字符串文字。
所以,这是一个可怕的选择。


结构标签类型...

结构标签类型与名义类型没有太大区别......

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

看那个。

  • 对于字符串文字来说太糟糕了。
  • 克服了字符串文字障碍后,还不错……对吧?

但是这个提议的主要用例是字符串文字。
所以,这是一个可怕的选择。

此外,这个结构标签类型示例是名义类型示例的文字(ha,双关语)复制粘贴

唯一的区别在于StripeChargeIdStripeRefundId类型的声明方式。

即使代码基本相同,结构类型也比名义类型要好。 (我会在下一篇文章中澄清这一点,我发誓)。


结论

这只是此评论的结论! 不是我的整体想法的结论!

字符串模式类型/正则表达式验证的字符串类型比名义/结构标签类型更符合人体工程学。 希望我的简单例子不会太做作,足以证明这一点。


结论(额外)

尽可能多地,采用原始类型子集的方法应始终优先于名义/结构标记/值对象类型。

采用原始类型子集的示例,

  • string文字
  • number文字(不包括NaN, Infinity, -Infinity
  • boolean文字
  • bigint文字
  • 甚至unique symbol也只是取symbol一个子集

在上面的例子中,只有boolean是“足够有限”的。 它只有两个值。
开发人员对truefalse文字感到满意,因为没有其他要求。


number类型是有限的,但它有很多值,我们不妨将其视为无限。
我们可以指定的文字也存在漏洞。

这就是为什么范围数字类型和NaN, Infinity, -Infinity问题如此受欢迎,并不断出现的原因。 能够从无限集合中指定一个小的有限值集合还不够好。

当某人需要指定无限集的大型有限/无限子集时,指定范围是最常见/最自然的想法之一。


bigint类型基本上是无限的,仅受内存限制。

它还有助于范围数字类型问题的流行。


string类型基本上是无限的,仅受内存限制。

这就是为什么这个字符串模式类型/正则表达式验证的字符串类型问题如此受欢迎的原因。

当某人需要指定无限集的大型有限/无限子集时,指定正则表达式是最常见/最自然的想法之一。


symbol类型...它也是无限的。 而且几乎是无限的。

但是symbol类型的元素几乎在所有方面都彼此无关。 而且,因此,没有人提出问题要问,“我可以有一种方法来指定symbol的大型有限/无限子集吗?”。

对大多数人来说,这个问题甚至没有意义。 没有有意义的方法来做到这一点(对吧?)


然而,仅仅能够声明基元的子集并不是很有用。 我们还需要,

  • 正确类型的文字必须无需进一步工作即可分配

谢天谢地,TS 足够理智,允许这样做。

想象一下无法将false传递给(arg : false) => void

  • 缩小的内置方法

    目前,对于这些文字,我们有=====作为缩小的内置方式。

    想象一下需要为每个文字编写一个新的类型保护!

名义/结构标签/值对象类型的问题在于它们基本上无法满足上述两个标准。 它们将原始类型转换为不完全是对象类型的笨重类型,但无论如何必须像对象类型一样处理。

人体工学

好的,这里有更多关于字符串模式、名义标签和结构标签类型的详细说明。

这些参数也适用于https://github.com/microsoft/TypeScript/issues/15480


跨库兼容性

标称类型在跨库兼容性方面最差
这就像在两个库中使用unique symbol并试图让它们进行互操作。
它根本无法做到。
您需要使用样板类型的防护或 trust-me-operator ( as )。

您还需要更多样板来作为断言保护程序。

如果类型不需要跨库兼容性,那么使用名义类型就可以了...
即使非常不符合人体工程学(见上面的例子)。


对于结构类型,如果库A有,

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

图书馆B有,

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

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

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

然后你需要一个样板类型的保护,或者 trust-me-operator ( as )。

您还需要更多样板来作为断言保护程序。

如果类型不需要跨库兼容性,那么使用结构类型就可以了...
即使非常不符合人体工程学(见上面的例子)。


对于字符串模式类型,如果库A有,

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

图书馆B有,

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

假设两个库总是为StripeChargeId生成满足两个库要求的字符串。 库A的验证只是“懒惰”。 库B的验证“更严格”。

然后,有点烦。 但也不算太糟。
因为你可以只使用libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId)作为 typeguard。 无需使用 trust-me-operator ( as )。

但是,您仍然需要用于断言保护的样板。

如果类型不需要跨库兼容性,那么使用字符串模式类型是完美的,而且也非常符合人体工程学。


如果您需要跨库兼容性,字符串模式类型仍然比结构标签类型更好! 听我说。

如果被建模的领域被很好地理解,那么很可能多个独立的库作者最终会编写相同的正则表达式。 使用结构化标签类型,他们都可以在标签中写入他们想要的任何属性和类型。

如果有一个标准为正在建模的任何东西指定字符串格式,那么基本上可以保证所有库作者都会编写相同的正则表达式! 如果他们编写不同的正则表达式,则他们并没有真正遵循标准。 你想使用他们的图书馆吗? 使用结构标签类型,他们仍然可以随便写什么。 (除非有人开始制定每个人都会关心的结构标签类型标准?大声笑)


跨版本兼容性

像往常一样,名义类型在跨版本兼容性方面是最差的
哦,你给你的图书馆打了补丁,还是次要版本?
类型声明还是一样?
代码还是一样?
不。 他们是不同的类型。

image


只要标签类型在结构上相同,结构标签类型仍然可以跨版本(甚至主要版本)分配。


只要正则表达式相同,字符串模式类型仍然可以跨版本(甚至主要版本)进行分配。

或者我们可以运行一个 PSPACE-complete 算法来确定正则表达式是否相同? 我们还可以确定哪些正则表达式的子类是最常见的,并为这些子类运行优化的等价算法……但这听起来很费劲。

正则表达式子类型检查会很酷,并且肯定会使使用字符串模式类型更符合人体工程学。 就像范围子类型检查如何有益于数字范围类型提案一样。

[编辑]
在这篇评论中,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

有人链接到,
https://bora.uib.no/handle/1956/3956

标题为“正则表达式的包含问题”
[/编辑]


样板

TODO(但我们可以看到字符串模式类型的样板数量最少)

文字调用

TODO(但我们可以看到字符串模式类型最支持文字调用)

非文字调用

TODO(但我们可以看到字符串模式类型支持非文字调用是最好的)

更多关于https://github.com/microsoft/TypeScript/issues/6579#issuecomment -542405537

TypeScript 不会在交叉点的实例化上出错,因此这不会成为任何最终设计的一部分。

我不知道为什么人们想要禁止十字路口,但你绝对正确,禁止它没有意义。


从而打破每一个非文字调用?

好吧,并不是每个非文字调用。

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
}

在无法证明它与正则表达式匹配的非文字调用上中断不一定是件坏事。 这只是一个类型安全的事情。

这有点像说字符串文字不好,因为现在非文字调用失败了。
字符串模式类型/正则表达式验证的字符串类型只是让您定义无限数量的字符串文字的联合。


任何非字面使用都需要重新测试或断言:

我根本不认为这是一个问题。
现在与名义/标记类型相同。
或者尝试将string传递给需要字符串文字的函数。
或者尝试将较宽的类型传递给较窄的类型。

在这种特殊情况下,您已经展示了const ZipCode = /^\d\d\d\d\d$/;ZipCode.test(s)可以充当类型保护。 这肯定会有助于人体工程学。


  • 正在解决的问题没有更好的替代方案(包括语言中尚未出现的合理替代方案)

好吧,希望我已经表明名义/结构标签类型不是更好的选择。 他们实际上很糟糕。

  • 该问题在实际代码库中以有意义的频率发生

呃...让我回到你的那个...

  • 建议的解决方案很好地解决了这个问题

提议的字符串模式类型似乎很不错。


TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

您的观点是名义/标记类型足以用于非文字使用。
因此,任何显示非文字用法的用例都不够好,因为名义/标记类型涵盖了它。

然而,我们已经看到,即使是非字面使用,

  • 名义/结构标签类型存在跨库/版本兼容性问题
  • 标称/结构标签类型的样板数量明显多于字符串模式类型的样板

此外,似乎提出的字面用例对您来说并不令人满意,因为它们试图做一些愚蠢的事情,例如电子邮件验证,或者使用不够准确的正则表达式。


编写测试 - 这是硬编码输入有意义的地方,尽管这几乎是对立的,因为您的测试代码可能应该提供大量无效输入

一个很好的用例是编写运行时测试。 你是对的,他们也应该为运行时测试抛出大量无效输入。

但这不是不支持字符串模式类型的理由。 可能是他们想要测试某个文件中的有效输入并意外提供无效输入的情况。

但是,因为他们必须使用类型保护或 trust-me-operator ( as ) 或值对象,现在他们会得到一个运行时错误,而不是提前知道测试会失败.

使用 trust-me-operator ( as ) 进行运行时测试应该只保留用于测试无效输入。 当想要测试有效输入时,更清楚的是不需要黑客将文字分配给名义/结构标签类型。

如果他们将来更改正则表达式,由于可分配性问题,如果他们的测试现在甚至无法运行,那就太好了。 如果他们只是在测试中到处使用as ,他们在运行测试之前不会知道。

如果库作者在对自己的库进行测试时到处都使用as ......下游消费者呢? 在升级到新版本时,他们会不会也很想在任何地方使用as并遇到运行时问题?

使用字符串模式类型,就没有必要在任何地方使用as ,库作者和下游消费者都将更容易知道破坏性更改。

(有点啰嗦,但我希望我的一些观点得到通过)。


此外,我编写了很多编译时测试(我知道 TS 团队也这样做)。

如果我可以测试某个string文字是否会在我的编译时测试中失败/通过正则表达式检查,那就太好了。 目前,我无法对这些事情进行编译时测试,而需要使用运行时测试。

如果它失败/通过我的编译时测试,那么我会相信下游消费者可以使用这些字符串文字(或类似的文字)并期望它们以正确的方式运行。


看起来这很快让我们走上了通往巴别塔的道路......

实际上,使用名义/结构标签类型更是如此。 正如上面的例子所示,它们在跨库/版本兼容性方面做得非常糟糕......

但是,正则表达式/字符串模式类型很有可能不会陷入这个问题(希望这要归功于标准化和理智的库作者)。


编辑

线程中的一个常见问题是这些正则表达式将有助于验证测试代码,因为即使在生产场景中代码将针对运行时提供的字符串而不是硬编码文字运行,您仍然需要一些验证,以确保您的测试字符串是“正确的”。 但是,这似乎是名义/标记/品牌字符串的参数,因为您将以任何一种方式编写验证函数,并且测试的好处是您知道它们可以详尽地运行(因此测试输入中的任何错误都会在开发周期的早期标记)。

啊...我应该在写这篇文章之前先阅读所有内容...

无论如何,我确实有一些例子,其中字符串模式类型很有用。


HTTP 路由声明库

使用此库,您可以构建 HTTP 路由声明对象。 此声明由客户端和服务器使用。

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

这些是.append()的约束,

  • 仅字符串文字(目前无法强制执行,但如果您使用非文字,路由声明构建器将变成垃圾)
  • 必须以正斜杠开头 ( / )
  • 不得以尾随正斜杠 ( / ) 结尾
  • 不得包含 conlon 字符 ( : ); 它是为参数保留的
  • 不得连续包含两个或多个正斜杠 ( // )

现在,我只有这些会引发错误的运行时检查。 我希望下游消费者不必阅读一些 Github README或 JSDoc 评论就必须遵循这些约束。 只需写下路径,就会看到红色的波浪线。


其他的东西

我也有十六进制字符串、字母数字字符串的正则表达式。

我也有这个

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

我看到这个,

整数 - 错误地拒绝“3e5”

我也有这个,它不是整数正则表达式,而是使用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,
    };
}

不过我也有这个评论

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

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

正则表达式构造函数

有趣的是, RegExp构造函数将受益于正则表达式验证的字符串类型!

眼下,是,

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

然而,我们本来可以,

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

TL;DR(请阅读它,不过,我为此付出了很多努力:cry:)

  • 字符串模式类型比名义/结构标签类型更符合人体工程学

    • 少样板

  • 字符串模式类型比名义/结构标签类型更不可能成为巴别塔的情况

    • 尤其是正则表达式子类型检查

  • 字符串模式类型是定义string类型的大型有限/无限子集的最自然的方式

    • 引入此功能甚至可能使人们更仔细地考虑其库的有效字符串格式!

  • 字符串模式类型为某些库提供了更强的编译时安全性(让我回到你的流行程度......逃跑

    • RegExp 构造函数、十六进制/字母数字字符串、路由路径声明、数据库的字符串标识符等。


为什么你的正则表达式如此糟糕?

其他人提出的一堆用例想引入字符串模式类型以适应现有库; 而且似乎并没有说服 TS 团队。

很多时候,我觉得这些现有的库甚至不会使用那么多的正则表达式来验证他们的输入。 或者,他们使用正则表达式来执行简单的验证。 然后,他们使用更复杂的解析器来执行实际验证。

但这是字符串模式类型的实际有效用例!


用于验证有效字符串值超集的字符串模式类型

当然,以/开头、不以/结尾、不包含连续的/且不包含:的字符串将通过“ HTTP 路径正则表达式”。 但这仅意味着传递此正则表达式的值是有效 HTTP 路径的

再往下,我们有一个实际的 URL 路径解析器,用于检查?未使用、 #未使用、某些字符已转义等。

但是通过这种简单的字符串模式类型,我们已经消除了库用户可能遇到的一大类常见问题! 我们也在编译时消除了它!

用户在其 HTTP 路径中使用?的情况并不常见,因为大多数人都有足够的经验知道?是查询字符串的开头。


我刚刚意识到您已经知道这个用例。

这个线程意味着各种各样的用例; 具体的例子已经比较少见了。 令人不安的是,其中许多示例似乎并不完整——它们使用的 RegExp 会拒绝有效输入。

所以,当然,很多提议的正则表达式并不“完整”。
但只要他们不拒绝有效输入,应该没问题,对吧?

如果他们允许无效输入,那没关系,对吧?
由于我们可以在运行时使用“真实”解析器来处理完整的验证。
并且编译时检查可以为下游用户排除很多常见问题,提高生产力。

那些拒绝有效输入的示例应该很容易修改,这样它们就不会拒绝有效输入,而是允许无效输入。


字符串模式类型和交集

无论如何,字符串模式类型上的交集类型将非常有用!

我的.append()示例可以写成,

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;

not PatternOf</\/\//>也可以是,
PatternOf</^((([/])(?!\3))|[^/])+$/>但这要复杂得多

谢谢@AnyhowStep的广泛演示。 我想批评你让我读了这么多,但结果却很有帮助!

我经常在输入充满字符串参数的内部 api 中挣扎,最终我不可避免地会遇到很多在运行时抛出的条件。 不可避免地,我的消费者需要重复这些模式检查,因为他们不想要异常,他们想要一种特殊的方式来处理失败。

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

在字符串和模式的世界中,泛型stringunknown几乎相同,删除了很多类型安全以支持运行时检查,并给我的消费开发人员带来不便。

对于提到的一些用例,只需要一小部分 Regex,例如前缀匹配。

可能这可以通过更通用的 TS 语言功能来完成,例如 Variadic Kinds #5453 和传播字符串文字类型时的类型推断。

未来推测:

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;

对于提到的一些用例,只需要一小部分 Regex,例如前缀匹配。

我仍然支持我的提议,它提供了这个 + 一些其他的东西,基本上是非常小的无星语言超集,你仍然可以合理有效地检查子集和平等。 到目前为止,我还没有看到任何其他提案试图解决任意正则表达式的性能方面,这是 TS 团队最关心的问题。

无星号语言的问题是,顾名思义,你不能使用星号,这使得验证诸如 url 之类的东西变得困难。 此外,大多数人可能会想要星星,并且只使用任意数量的重复序列来模拟它们,这将很难检查子集。

大多数正常的 DFA 可表示正则表达式的性能并没有那么糟糕,并且可以检查它们的子集/超集。

不过,您仍然可以得到*

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

@TijmenW 仔细阅读我的提案 - 那里有一些隐藏的基本原理,以及一些使其实际实用的小功能。 它不直接限于指定无星文法,而是一个小的超集,扩展到刚好足以使它对我的半高级用例实际有用。 特别是,您可以为单个字符执行starof ('a' | 'b' | ...)并且您可以将string用作等效于starof UnionOfAllCodePoints (实际上使其不再是理论上的原始值)。

此外,检查正则语言是否匹配另一种正则语言匹配的子集是 NP 完全的,等效于一般子图同构问题。 这就是为什么你不能只使用标准的常规语言,以及为什么我试图尽可能地限制starof ,以尽量降低理论上的计算复杂度。

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

对此持保留态度,因为它是一个全新的库,但是像https://github.com/ostrowr/ts-json-validator这样的任何库都可以通过诸如正则表达式类型的东西变得更有用。

该库的目标是生成 Typescript 类型/JSON 模式对<T, s>使得

  1. s可以验证的任何类型都可以分配给T
  2. 当针对s运行时,可分配给T类型越少越好。

正则表达式类型将通过允许验证类型至少对以下关键字更严格来提高 (2) 的严格性:

  • format
  • patternProperties
  • propertyNames

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们。

所有 Excel 接口库都可以将类型验证用作A1A5:B7

属性键/正则表达式字符串索引器

一些库根据属性名称处理对象。 例如,在 React 中,我们希望将类型应用于名称以aria-开头的任何 prop:

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

这实际上是一个正交概念(我们可以添加 Regex 类型而不添加 Regex 属性键,反之亦然)。

我知道这与这里发生的一切有点正交,但 Wesley 认为你可以使用我们的输入。 由于多种原因,这在 Fabric 中不断出现。 作为组件库,我们希望能够提升一个组件 props 接口,准确反映 TypeScript 允许的 React 组件接口,包括data-aria-属性。 没有它,我们就无法将准确的接口提升

如果有什么我们可以帮忙的,请告诉我! 😄

TS游乐场

import * as React from 'react';

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

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

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

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

TODO:请通过识别可以从 RegExp 类型中受益的真实库函数以及您将使用的实际表达式来帮助我们

Cron 工作。 (很惊讶没有提到这一点)

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

只需在这里投入我的两分钱 - 我正在做一个 React 项目,我们想验证一个将用作 HTML id属性的道具。 这意味着它必须满足以下规则,否则会发生意外行为:

  1. 至少有一个角色
  2. 没有空格

换句话说:

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

另一个示例:带有预期格式'<namespace>/<name>[@<version>]'字符串的sanctuary-type-identifiers

用例:字符串类型的 DOM API,如Navigator.registerProtocolHandler()

引用 MDN:

出于安全原因, registerProtocolHandler()限制了可以注册的方案。

只要满足以下条件,就可以注册自定义方案

  • 自定义方案的名称以web+开头
  • 自定义方案的名称在web+前缀后至少包含 1 个字母
  • 自定义方案的名称中只有小写 ASCII 字母。

换句话说, Navigator.registerProtocolHandler()需要一个众所周知的string或一个自定义的string但前提是它符合特定的模式。

CSSType 的CSS 自定义属性是另一个用例,可为所有属性提供封闭类型,但前缀为--属性除外。

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

相关https://github.com/frenic/csstype/issues/63

有人能告诉我这是否与细化类型相同吗? https://github.com/microsoft/TypeScript/issues/7599

@gautam1168理论上它只是一个子集,它专门改进字符串类型。 (当然,数字类型有其自身的关注点。)

对于提到的一些用例,只需要一小部分 Regex,例如前缀匹配。

我仍然支持我的提议,它提供了这个 + 一些其他的东西,基本上是非常小的无星语言超集,你仍然可以合理有效地检查子集和平等。 到目前为止,我还没有看到任何其他提案试图解决任意正则表达式的性能方面,这是 TS 团队最关心的问题。

在这篇评论中,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

有人链接到,
https://bora.uib.no/handle/1956/3956

标题为“正则表达式的包含问题”


然而,

  • 如果右侧表达式是 1-unambiguous,则算法给出正确答案。
  • 否则,它可能给出正确的答案,或者没有答案。

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

(当然,JS正则表达式是非常规的)

@AnyhowStep这可能会奏效 - 我只是解除starof限制并相应地更改该限制。 我想要一种更好的方法来描述限制,因为数学有点抽象,目前还不清楚它在实践中如何具体应用(并不是每个使用这些类型的人都精通正式语言)。

另外,另外,我非常喜欢starof的更好替代方案,作为对此类事物进行建模的运算符。

我很好奇:是否可以决定包含/包含正则表达式? 根据 do wikipedia ,它是可判定的。 然而,这是否也解释了 JS 中的正则表达式? 我认为它们比标准 RE 具有更多功能(例如反向引用)。 如果它是可判定的,它在计算上是否可行?
这会影响此功能(缩小):

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

@nikeee Decidable 不足以让这成为现实。 在这种规模下,即使是二次时间通常也太慢了。 不是 TS,但我确实对类似问题有一些背景。

面对反向引用,我怀疑它仍然是可判定的,但如果不是更糟的话,它可能是指数级的。 不过,这只是一个有根据的猜测。

感谢您澄清这一点!

在这种规模下,即使是二次时间通常也太慢了。

这就是为什么我还问它在计算上是否可行,所以我猜不是。

如果同样适用于平等,那是不是意味着这个特征的几乎所有属性都是不可行的? 纠正我,如果我错了,但似乎唯一剩下的就是会员资格。 我不认为仅此一项会有用。

@nikeee值得记住的是,这种模式将根据与它进行比较的每种类型的每个属性进行检查。 而对于类型的正则表达式的属性,必须计算正则表达式是否有什么其他的正则表达式匹配,在其本身一个相当复杂的野兽的一个子集相匹配。

这不是不可能,只是很难,如果你想让它可行,你就必须加以限制。 (一方面,JS 正则表达式不起作用——它们不仅可扩展性不够,而且过于灵活。)

编辑:我确实想重申这一点:我不在TS 团队中,只是为了澄清。 我只是在 CS 算法设计方面有不错的背景。

嗯,所以也许您只能支持“通常”正则表达式的“有限”子集。 鉴于用例,到目前为止,正则表达式非常简单......(颜色,电话号码等)

我们如何设计仅支持一个子集的用户体验? 用户可能不太清楚 RegEx 的功能 X 有效,但 Y 无效。

好吧……不要称之为“正则表达式”——首先。 也许只是“模式匹配”左右:see_no_ evil:。 但是,是的,可能不是一件容易的事......

像这样的非正则表达式语法怎么样:

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'

在我看来,这非常适合类型系统。 您可以为诸如可选匹配之类的内容添加不同的语法:

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

添加对量词、贪婪运算符的支持,您就会得到一些非常强大的东西,我认为这对于大多数开发人员可能想要使用它的用例来说可能就足够了。

我认为这种方法会更加用户友好。 然而,它似乎等同于对类型的算术运算。
根据https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 和https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109,不做是设计决定类型的算术。
如果我没记错的话,这种方法很容易创建一个巨大的类型。 考虑:

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

(这假设实现将使用联合类型。它可能适用于不同/更复杂的实现)

免责声明:我不是 TS 团队的一员,也不在 TS 上工作。 只是我的2c。

@rozzzly @nikeee这或多或少是我提案的精髓,只是缺少一些较小的功能。 我基于正则语言的一大子集(形式语言概念),而不是正则表达式文字等意义上的正则表达式,因此它的功能远不如那些强大,但足以完成工作。

我认为这种方法会更加用户友好。 然而,它似乎等同于对类型的算术运算。

Math 说验证一个类型是否是另一个类型的子类型在计算上等同于检查一个字符串是否包含在给定的形式语言中。

如果您还检查 TLD/公共后缀的有效性,那么域验证实际上是一件非常复杂的事情。 根据 RFC 的通用域本身就像/[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + 最多 255 个字符一样简单,但即使这样输入也非常复杂,除非您像上面的正则表达式所展示的那样使用完整的常规语法。 您可以仅使用来自@rozzzly或我的提案的字符串以非常直接的方式生成类型(我将其作为练习留给读者),但最终结果仍然相当复杂。

@isiahmeadows

这或多或少是我提案的精髓,只是缺少一些较小的功能。

我最后一次通读整个线程是一年多前。 我在休息时看到了一条通知,阅读@rugk关于 _“好吧......不要称之为“正则表达式”——对于初学者来说“_ 这让我开始思考......我没有意识到有人已经为本质上相同的_(/一个非常相似的)_ 想法提出了一个相当详细的提案。

...即使键入也非常复杂,除非您像上面的正则表达式所展示的那样使用完整的常规语法。 您可以仅使用来自@rozzzly或我的提案的字符串以非常直接的方式生成类型(我将其作为练习留给读者),但最终结果仍然相当复杂。

在我看来,我建议允许的一些用于有限模式匹配的工具对于非常简单且_必须不严格_严格的类型非常有用。 我给出的示例远非精确,并且不会炸毁编译器。

但正如@nikeee和你们都指出的那样,这可能会被带到危险的极端。 假设一个最简单的实现只支持联合。 有人会破坏每个人的一天,发布对@types/some-popular-project的更新,其中包含:

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

从这个角度来看,该联合包括可观测宇宙中

现在,我已经看到了一些非常长的可分配性错误,但想象一下(未截断的)错误......

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

所以是的..那里有一些问题

@rozzzly是什么使该类型与TupleWithLengthBeteen1And64<Charset>不同(就可行性而言)?
编译器不会被迫将每种类型都扩展为规范化形式,如果这样做,它会很快在相当正常的类型上爆炸。
并不是说我认为这个问题目前在打字稿中是有意义的,即使“3 到 1024 之间的整数”(想想消息缓冲区分配长度)也被认为超出了范围。

@simonbuchan如果没有别的,至少需要存在前缀和后缀类型。 这本身就是许多 DOM 库和框架所必需的。

我知道这已经被打死了,并且已经给出了一些好的建议。 但我只是想添加一些额外的东西,有些人可能会觉得有点有趣。

面对反向引用,我怀疑它仍然是可判定的,但如果不是更糟的话,它可能是指数级的。 不过,这只是一个有根据的猜测。

反向引用可以使正则表达式描述上下文相关的文法,上下文无关文法的超集。 CFG 的语言平等是不可判定的。 所以对于 CSG 来说就更糟了,它相当于线性有界自动机。


假设所有可以转换为 DFA 的正则表达式都用于正则表达式(concat、union、star、intersection、complement 等),将正则表达式转换为 NFA 是 O(n),得到两个的乘积NFA 是 O(m*n),然后遍历接受状态的结果图是 O(m*n)。 因此,检查两个正则表达式的语言相等性/子集也是 O(m*n)。

问题是这里的字母表真的很大。 在谈论 DFA/NFA/正则表达式时,教科书通常将自己限制为 1-5 大小的字母。 但是对于 JS regexp,我们将所有的 unicode 作为我们的字母表。 诚然,使用稀疏数组和其他巧妙的技巧和优化等式/子集测试可以有有效的方法来表示转换函数......

我相信可以稍微有效地对常规到常规分配进行类型检查。

然后,所有非常规赋值都可以只需要显式类型断言。

我最近在做一个小的有限自动机项目,所以我脑海中的信息仍然很新鲜 =x

如果我没记错的话,这种方法很容易创建一个巨大的类型。 考虑:

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

(这假设实现将使用联合类型。它可能适用于不同/更复杂的实现)

有趣的是,这正是新模板字符串文字类型所可能实现的。 这种情况似乎可以通过为联合类型设置阈值来避免。

@AnyhowStep JS 反向引用是唯一一个上下文敏感的产品(并且是一个相当简单且有限的产品 - 最多只能像这样引用 9 个组),并且正则表达式语法的其余部分是常规的,所以这就是我怀疑它的原因是可判定的。 但无论如何,我认为我们可以同意它在任何意义上都不实用。 🙂

编辑:准确性

我确认@rozzzly 的这条评论每晚都适用于 TS 4.1.0!

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

在playground里试一下,看到fail有编译时错误🤩


更新:稍微使用此功能后,它不会涵盖很多用例。 例如,它不适用于十六进制颜色字符串。

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

今天,这失败了“表达式产生了一个太复杂而无法表示的联合类型。(2590)”

我确认@rozzzly 的这条评论每晚都适用于 TS 4.1.0!

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

在playground里试一下,看到fail有编译时错误🤩

如果它可以应用于索引,这将解决我们大多数人在 UX 库中面临的数据或咏叹调问题。

_更新_:稍微玩一下这个功能后,它不会涵盖很多用例。 例如,它不适用于十六进制颜色字符串。

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

今天,这失败了“表达式产生了一个太复杂而无法表示的联合类型。(2590)”

发行说明中对此限制有一些参考。 它创建所有可能的有效组合的列表,在这种情况下,它将创建一个具有 16,777,216(即 16^6)个成员的联合。

这是一个好主意……Igmat 在 2016 年发表了一些令人难以置信的帖子,无论如何在纸上看起来都不错。

我发现这个是因为我想确保传递给我的函数的对象文字的键是有效的 css 类名。 我可以在运行时轻松检查...但对我来说似乎很明显打字稿应该能够在编译时做到这一点,特别是在我只是硬编码对象文字和打字稿不应该弄清楚是否MyUnionExtendedExotic类型满足 SomeArbitraryRegexType。

也许有一天我会知识渊博,做出更有成效的贡献:/

我确认@rozzzly 的这条评论每晚都适用于 TS 4.1.0!

哇。 老实说,我没想到这会得到实施,至少不会很快。

@chadlavi-casebook

发行说明中对此限制有一些参考。 它创建所有可能的有效组合的列表,在这种情况下,它将创建一个具有 16,777,216(即 16^6)个成员的联合。

我很想知道在它成为性能问题之前该联合可以得到多大。 @styfle的例子展示了达到那个天花板是多么容易。 显然,复杂类型的有用性与性能的回报在一定程度上会递减。

@thehappycheese

我想确保传递给我的函数的对象文字的键是有效的 css 类名

我相当有信心地说,目前的实现是不可能的。 如果支持量词和范围,您可能会得到 BEM 样式类名称的验证。 标准的 js 正则表达式并不_太_可怕:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
您还可以放弃锚点,因为就实现而言,它要么是端到端匹配,要么什么都没有,所以^$是隐含的。 现在这是一个相对简单的正则表达式,适用于有效 css 选择器的一小部分。 例如: ಠ_ಠ是一个有效的类名。 我不是在开玩笑。

抱歉。 我不得不这样做。

我在 TypeScript 中实现了常规语言。

更准确地说,我使用 TS 4.1 实现了一个简单的确定性有限自动机

我的意思是,我们已经可以在 TS 中实现图灵机了。 因此,与此相比,DFA 和 PDA“容易”。

模板字符串使这更有用。


核心类型实际上很简单,适合 < 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>;

指定自动机是困难的部分。

但我很确定有人可以为TypeScript DFA™生成器制作正则表达式...


我还想强调,“长度为 6 的十六进制字符串”示例表明您可以使用丑陋的hackery 使函数参数只接受与正则表达式匹配的字符串,

declare function takesOnlyHex<StrT extends string> (
    hexString : Accepts<HexStringLen6, StrT> extends true ? StrT : {__err : `${StrT} is not a hex-string of length 6`}
) : void;

//OK
takesOnlyHex("DEADBE")

//Error: Argument of type 'string' is not assignable to parameter of type '{ __err: "DEADBEEF is not a hex-string of length 6"; }'.
takesOnlyHex("DEADBEEF")

//OK
takesOnlyHex("01A34B")

//Error: Argument of type 'string' is not assignable to parameter of type '{ __err: "01AZ4B is not a hex-string of length 6"; }'.
takesOnlyHex("01AZ4B")

这是一个额外的游乐场; 它实现了正则表达式/^hello .*/

还有另一个游乐场; 它实现了正则表达式/ world$/

最后一个例子,游乐场; 这是一个浮点字符串正则表达式

@AnyhowStep好吧,我使用您的 DFA 想法来实现一个简单的正则表达式[abc]{4} ,这意味着字母 abc 以任何顺序丢失,但长度正好为 4。(aaaa、abcc、bbcc 等...)。
操场

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

https://github.com/Cyber​​ZHG/toolbox

如果我有更多的意志力,我会抓住上面的东西并用它来将正则表达式转换为 TS DFA™ 哈哈

好吧,我只是拼凑了一个原型,

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

[编辑] https://glitch.com/~efficacious-valley-repair <-- 这为更复杂的正则表达式产生更好的输出

[编辑] 似乎 Glitch 会归档闲置时间过长的免费项目。 所以,这是一个包含文件的 git repo,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

第 1 步,在此处输入正则表达式,
image

第二步,点击转换,
image

第三步,点击生成的TS Playground URL,
image

第 4 步,向下滚动到InLanguage_0
image

第 5 步,玩输入值,
image

image

https://www.npmjs.com/package/regex2dfa 的作者@kpdyer 致谢,感谢他完成了繁重的转换工作

如果有人需要更强大的东西,这里有一个图灵机😆

操场

这个线程读得太长了,许多评论要么是由模板文字类型解决的,要么是偏离主题的。 我创建了一个新问题 #41160,用于讨论此功能可能启用的剩余用例。 欢迎在这里继续讨论类型系统解析器 😀

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

weswigham picture weswigham  ·  3评论

Zlatkovsky picture Zlatkovsky  ·  3评论

wmaurer picture wmaurer  ·  3评论

DanielRosenwasser picture DanielRosenwasser  ·  3评论

MartynasZilinskas picture MartynasZilinskas  ·  3评论