Typescript: Saran: Jenis string yang divalidasi dengan ekspresi reguler

Dibuat pada 22 Jan 2016  ·  146Komentar  ·  Sumber: microsoft/TypeScript

Ada kasus, di mana properti tidak bisa sembarang string (atau satu set string), tetapi harus cocok dengan sebuah pola.

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

Ini adalah praktik umum dalam JavaScript untuk menyimpan nilai warna dalam notasi css, seperti dalam refleksi gaya css dari node DOM atau berbagai perpustakaan pihak ketiga.

Bagaimana menurutmu?

Literal Types Needs Proposal Suggestion

Komentar yang paling membantu

Proposal Desain

Ada banyak kasus ketika pengembang membutuhkan nilai yang lebih spesifik daripada hanya string, tetapi tidak dapat menghitungnya sebagai gabungan dari literal string sederhana misalnya warna css, email, nomor telepon, Kode Pos, ekstensi kesombongan , dll. Bahkan spesifikasi skema json yang biasanya digunakan untuk menggambarkan skema objek JSON memiliki pattern dan patternProperties yang dalam istilah sistem tipe TS bisa disebut regex-validated string type dan regex-validated string type of index .

Sasaran

Memberikan pengembang sistem tipe yang selangkah lebih dekat dengan Skema JSON, yang biasa digunakan oleh mereka dan juga mencegah mereka melupakan pemeriksaan validasi string saat dibutuhkan.

Ikhtisar sintaksis

Implementasi fitur ini terdiri dari 4 bagian:

Jenis tervalidasi 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;

Jenis variabel yang divalidasi oleh ekspresi reguler

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

dan sama, tetapi lebih mudah dibaca

let fontColor: CssColor;

Jenis indeks variabel yang divalidasi 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;
}

dan sama, tetapi lebih mudah dibaca

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

Ketik penjaga untuk tipe variabel

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

dan sama

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

dan menggunakan tipe yang ditentukan untuk keterbacaan yang lebih baik

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

sama dengan

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

Ketik gurard untuk tipe indeks

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

sama dengan

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
}

dan menggunakan tipe yang ditentukan untuk keterbacaan yang lebih baik

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

sama dengan

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

Ikhtisar semantik

Tugas

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

Sayangnya kami tidak dapat memeriksa apakah satu regex adalah subtipe dari yang lain tanpa dampak kinerja yang keras karena artikel ini . Jadi harus dibatasi. Tetapi ada solusi selanjutnya:

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

Sayangnya penetapan variabel string ke variabel regex-validated juga harus dibatasi, karena tidak ada jaminan dalam waktu kompilasi bahwa itu akan cocok dengan regex.

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

Tapi kita bisa menggunakan cast atau type guard eksplisit seperti yang ditunjukkan di sini . Kedua direkomendasikan.
Untungnya ini bukan kasus untuk literal string, karena saat menggunakannya, kami DAPAT memeriksa apakah nilainya cocok dengan regex.

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

Ketik penyempitan untuk indeks

Untuk kasus sederhana dari indeks regex-validated type lihat Type gurard untuk index type .
Tetapi mungkin ada kasus yang lebih rumit:

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

Literal tidak memiliki masalah seperti itu:

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

Tetapi untuk variabel, opsi terbaik adalah menggunakan pelindung tipe seperti pada contoh yang lebih realistis berikutnya:

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

Tetapi jika kita akan menggunakan definisi yang lebih baik untuk tipe Gmail itu akan memiliki penyempitan tipe lain:

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

Serikat pekerja dan persimpangan

Sebenarnya tipe umum dan tipe regex-validated sangat berbeda, jadi kita perlu aturan bagaimana menangani penyatuan dan persimpangan mereka dengan benar.

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
}

Obat generik

Tidak ada kasus khusus untuk generik, jadi tipe regex-validated dapat digunakan dengan generik dengan cara yang sama seperti tipe biasa.
Untuk obat generik dengan batasan seperti di bawah ini, tipe regex-validated berperilaku seperti string:

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

Keluarkan ikhtisar

Tidak seperti tipe biasanya, regex-validated berdampak pada emisi:

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

akan dikompilasi ke:

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

Ikhtisar kompatibilitas

Fitur ini tidak memiliki masalah dengan kompatibilitas, karena hanya ada kasus yang dapat merusaknya dan terkait dengan jenis regex-validated telah mengeluarkan dampak tidak seperti jenis biasanya, jadi ini adalah kode TS yang valid:

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

ketika kode di bawah ini tidak:

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

Tetapi kedua sudah WS tidak valid, tetapi karena alasan lain (jenis deklarasi salah).
Jadi sekarang kita harus membatasi mendeklarasikan variabel dengan nama yang sama dengan tipe, jika tipe ini adalah regex-validated .

PS

Jangan ragu untuk menunjukkan hal-hal yang mungkin saya lewatkan. Jika Anda menyukai proposal ini, saya dapat mencoba membuat tes yang mencakupnya dan menambahkannya sebagai PR.

Semua 146 komentar

Ya, saya telah melihat ini menyisir PastiTyped, . Bahkan kita bisa menggunakan sesuatu seperti ini dengan ScriptElementKind di lapisan layanan , di mana kita idealnya dapat menggambarkan ini sebagai daftar string tertentu yang dipisahkan koma.

Masalah utamanya adalah:

  • Tidak jelas bagaimana menyusunnya dengan baik. Jika saya ingin daftar yang dipisahkan koma dari "cat" , "dog" , dan "fish" , maka saya perlu menulis sesuatu seperti /dog|cat|fish(,(dog|cat|fish))*/ .

    • Jika saya sudah memiliki tipe yang menjelaskan tipe literal string untuk "cat" , "dog ", dan "fish" , bagaimana cara mengintegrasikannya ke dalam regex ini?

    • Jelas ada pengulangan di sini, yang tidak diinginkan. Mungkin memperbaiki masalah sebelumnya akan membuat ini lebih mudah.

  • Ekstensi non-standar membuat semacam ini rapuh.

+1 besar untuk ini, ZipCode, SSN, ONet, banyak kasus penggunaan lainnya untuk ini.

Saya menghadapi masalah yang sama, dan saya melihat itu belum diterapkan, mungkin solusi ini akan membantu:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Seperti yang disarankan @mhegazy, saya akan memasukkan saran saya (#8665) di sini. Bagaimana dengan mengizinkan fungsi validasi sederhana dalam deklarasi tipe? Sesuatu seperti itu:

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

Nilai yang dapat diterima oleh tipe akan ditentukan oleh tipe parameter fungsi dan oleh evaluasi fungsi itu sendiri. Itu akan menyelesaikan #7982 juga.

@rylphs +1 ini akan membuat TypeScript sangat kuat

Bagaimana cara kerja subtipe dengan _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?

di mana RegExType_1 dan RegExType_2 adalah _tipe string yang divalidasi regex_.

Sunting: Sepertinya masalah ini dapat diselesaikan dalam waktu polinomial (lihat Masalah Penyertaan untuk Ekspresi Reguler ).

Juga akan membantu dengan TypeStyle : https://github.com/typestyle/typestyle/issues/5 :rose:

Di BEJ, @RyanCavanaugh dan saya telah melihat orang menambahkan atribut aria- (dan berpotensi data- ). Seseorang benar-benar menambahkan tanda tangan indeks string di PastiTyped sebagai catch-all. Tanda tangan indeks baru untuk ini akan sangat membantu.

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

Proposal Desain

Ada banyak kasus ketika pengembang membutuhkan nilai yang lebih spesifik daripada hanya string, tetapi tidak dapat menghitungnya sebagai gabungan dari literal string sederhana misalnya warna css, email, nomor telepon, Kode Pos, ekstensi kesombongan , dll. Bahkan spesifikasi skema json yang biasanya digunakan untuk menggambarkan skema objek JSON memiliki pattern dan patternProperties yang dalam istilah sistem tipe TS bisa disebut regex-validated string type dan regex-validated string type of index .

Sasaran

Memberikan pengembang sistem tipe yang selangkah lebih dekat dengan Skema JSON, yang biasa digunakan oleh mereka dan juga mencegah mereka melupakan pemeriksaan validasi string saat dibutuhkan.

Ikhtisar sintaksis

Implementasi fitur ini terdiri dari 4 bagian:

Jenis tervalidasi 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;

Jenis variabel yang divalidasi oleh ekspresi reguler

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

dan sama, tetapi lebih mudah dibaca

let fontColor: CssColor;

Jenis indeks variabel yang divalidasi 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;
}

dan sama, tetapi lebih mudah dibaca

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

Ketik penjaga untuk tipe variabel

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

dan sama

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

dan menggunakan tipe yang ditentukan untuk keterbacaan yang lebih baik

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

sama dengan

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

Ketik gurard untuk tipe indeks

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

sama dengan

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
}

dan menggunakan tipe yang ditentukan untuk keterbacaan yang lebih baik

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

sama dengan

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

Ikhtisar semantik

Tugas

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

Sayangnya kami tidak dapat memeriksa apakah satu regex adalah subtipe dari yang lain tanpa dampak kinerja yang keras karena artikel ini . Jadi harus dibatasi. Tetapi ada solusi selanjutnya:

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

Sayangnya penetapan variabel string ke variabel regex-validated juga harus dibatasi, karena tidak ada jaminan dalam waktu kompilasi bahwa itu akan cocok dengan regex.

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

Tapi kita bisa menggunakan cast atau type guard eksplisit seperti yang ditunjukkan di sini . Kedua direkomendasikan.
Untungnya ini bukan kasus untuk literal string, karena saat menggunakannya, kami DAPAT memeriksa apakah nilainya cocok dengan regex.

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

Ketik penyempitan untuk indeks

Untuk kasus sederhana dari indeks regex-validated type lihat Type gurard untuk index type .
Tetapi mungkin ada kasus yang lebih rumit:

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

Literal tidak memiliki masalah seperti itu:

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

Tetapi untuk variabel, opsi terbaik adalah menggunakan pelindung tipe seperti pada contoh yang lebih realistis berikutnya:

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

Tetapi jika kita akan menggunakan definisi yang lebih baik untuk tipe Gmail itu akan memiliki penyempitan tipe lain:

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

Serikat pekerja dan persimpangan

Sebenarnya tipe umum dan tipe regex-validated sangat berbeda, jadi kita perlu aturan bagaimana menangani penyatuan dan persimpangan mereka dengan benar.

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
}

Obat generik

Tidak ada kasus khusus untuk generik, jadi tipe regex-validated dapat digunakan dengan generik dengan cara yang sama seperti tipe biasa.
Untuk obat generik dengan batasan seperti di bawah ini, tipe regex-validated berperilaku seperti string:

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

Keluarkan ikhtisar

Tidak seperti tipe biasanya, regex-validated berdampak pada emisi:

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

akan dikompilasi ke:

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

Ikhtisar kompatibilitas

Fitur ini tidak memiliki masalah dengan kompatibilitas, karena hanya ada kasus yang dapat merusaknya dan terkait dengan jenis regex-validated telah mengeluarkan dampak tidak seperti jenis biasanya, jadi ini adalah kode TS yang valid:

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

ketika kode di bawah ini tidak:

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

Tetapi kedua sudah WS tidak valid, tetapi karena alasan lain (jenis deklarasi salah).
Jadi sekarang kita harus membatasi mendeklarasikan variabel dengan nama yang sama dengan tipe, jika tipe ini adalah regex-validated .

PS

Jangan ragu untuk menunjukkan hal-hal yang mungkin saya lewatkan. Jika Anda menyukai proposal ini, saya dapat mencoba membuat tes yang mencakupnya dan menambahkannya sebagai PR.

Saya lupa menunjukkan beberapa kasus untuk persimpangan dan gabungan dari tipe regex-validated , tetapi saya telah menjelaskannya dalam kasus uji terbaru. Haruskah saya memperbarui Design proposal untuk mencerminkan perubahan kecil itu?

@Igmat , pertanyaan tentang proposal desain Anda: Bisakah Anda menguraikan ikhtisar emisi? Mengapa tipe yang divalidasi regex perlu dipancarkan? Sejauh yang saya tahu, tipe lain tidak mendukung pemeriksaan runtime ... apakah saya melewatkan sesuatu?

@alexanderbird , ya, jenis lain apa pun tidak berdampak pada emisi. Pada awalnya, saya pikir regex-validated akan melakukannya juga, jadi saya mulai membuat proposal dan bermain dengan sintaks yang diusulkan.
Pendekatan pertama seperti ini:

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

dan ini:

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

Tidak apa-apa dan tidak perlu memancarkan perubahan, karena "#000" dapat diperiksa dalam waktu kompilasi.
Tapi kita juga harus menangani penyempitan dari tipe string ke regex-validated agar bisa berguna. Jadi saya sudah memikirkan ini untuk kedua pengaturan sebelumnya:

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

Jadi itu juga tidak berdampak pada emisi dan terlihat ok, kecuali bahwa regex tidak terlalu mudah dibaca dan harus disalin di semua tempat, sehingga pengguna dapat dengan mudah membuat kesalahan. Tetapi dalam kasus khusus ini tampaknya masih lebih baik daripada mengubah cara kerja type .
Tapi kemudian saya menyadari bahwa hal ini:

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

adalah mimpi buruk. Dan itu bahkan tanpa persimpangan dan serikat pekerja. Jadi untuk menghindari terjadinya hal seperti ini, kita harus sedikit mengubah emit type seperti yang ditunjukkan pada proposal.

@DanielRosenwasser , bisakah Anda memberikan umpan balik untuk proposal ini? Dan juga untuk tes yang dirujuk di sini, jika memungkinkan?
Saya benar-benar ingin membantu mengimplementasikan fitur ini, tetapi itu membutuhkan banyak waktu ( tsc adalah proyek yang sangat rumit dan saya masih harus bekerja untuk memahami cara kerjanya di dalam) dan saya tidak tahu apakah itu proposal ini siap diimplementasikan atau Anda akan menolak fitur ini diimplementasikan dengan cara ini karena visi desain bahasa lain atau alasan lainnya.

Hai @Igmat , saya pikir ada beberapa hal yang seharusnya saya tanyakan pada awalnya

Untuk memulai, saya masih tidak mengerti mengapa Anda memerlukan perubahan apa pun untuk dipancarkan, dan saya tidak berpikir emisi apa pun berdasarkan tipe akan dapat diterima. Lihat non-tujuan kami di sini .

Masalah lain yang seharusnya saya kemukakan adalah masalah ekspresi reguler yang menggunakan referensi balik. Pemahaman saya (dan pengalaman) adalah bahwa referensi balik dalam ekspresi reguler dapat memaksa tes untuk berjalan dalam waktu eksponensial terhadap inputnya. Apakah ini kasus sudut? Mungkin, tapi itu adalah sesuatu yang saya lebih suka hindari secara umum. Hal ini sangat penting mengingat bahwa dalam skenario editor, pemeriksaan tipe di suatu lokasi harus memakan waktu minimal.

Masalah lainnya adalah kita harus bergantung pada mesin yang dijalankan oleh kompiler TypeScript, atau membangun mesin ekspresi reguler khusus untuk menjalankan hal-hal ini. Misalnya, TC39 bergerak untuk menyertakan tanda s sehingga . dapat cocok dengan baris baru. Akan ada perbedaan antara EXXXXX dan runtime lama yang mendukung ini.

@igmat - tidak ada pertanyaan dalam pikiran saya bahwa memiliki regex yang dipancarkan saat runtime akan berguna. Namun, saya tidak berpikir mereka perlu agar fitur ini berguna (dan dari suara apa yang dikatakan @DanielRosenwasser , itu mungkin tidak akan disetujui). Kamu berkata

Tetapi kita juga harus menangani penyempitan dari string ke tipe yang divalidasi regex untuk membuatnya berguna

Saya pikir ini hanya terjadi jika kita ingin mempersempit dari string dinamis ke tipe yang divalidasi regex. Ini menjadi sangat rumit. Bahkan dalam kasus sederhana ini:

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

Kami tidak dapat memastikan bahwa jenisnya akan cocok - bagaimana jika jumlahnya negatif? Dan ketika regex menjadi lebih rumit, itu menjadi semakin berantakan. Jika kita benar-benar menginginkan ini, mungkin kita mengizinkan "ketik interpolasi: type Baz = /prefix:{number}/ ... tapi saya tidak tahu apakah layak untuk pergi ke sana.

Sebagai gantinya, kita bisa mencapai sebagian tujuan jika kita hanya mengizinkan literal string untuk ditetapkan ke tipe yang divalidasi regex.

Pertimbangkan hal berikut:

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'

Apakah menurut Anda itu alternatif yang bisa diterapkan?

@DanielRosenwasser , Saya telah membaca Tujuan Desain dengan cermat dan, jika saya memahami Anda dengan benar, masalahnya adalah pelanggaran Non-tujuan#5.
Tetapi bagi saya itu bukan pelanggaran, tetapi sebagai peningkatan sintaksis. Misalnya, sebelumnya kami memiliki:

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
}

Dengan penerapan proposal ini akan terlihat seperti:

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
}

Seperti yang Anda lihat, kodenya hampir sama - ini adalah penggunaan regex yang umum dan sederhana. Tetapi kasus kedua jauh lebih ekspresif dan akan mencegah pengguna dari kesalahan yang tidak disengaja, seperti lupa memeriksa string sebelum menetapkannya ke variabel yang dimaksudkan untuk divalidasi secara regex.
Hal kedua adalah bahwa tanpa penyempitan tipe seperti itu, kami biasanya tidak dapat menggunakan tipe yang divalidasi regex dalam indeks, karena dalam kebanyakan kasus, bidang indeks tersebut berfungsi dengan beberapa variabel yang tidak dapat diperiksa saat runtime karena dapat dilakukan dengan literal .

@alexanderbird , saya tidak menyarankan membuat kode ini valid atau menambahkan beberapa pemeriksaan tersembunyi di runtime dan waktu kompilasi.

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

Kode ini harus membuang kesalahan karena proposal saya. Tapi ini:

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

atau ini:

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

akan benar, dan bahkan tidak berdampak pada kode yang dipancarkan.

Dan seperti yang saya tunjukkan di komentar sebelumnya, literal pasti tidak cukup bahkan untuk kasus penggunaan umum, karena kita sering harus bekerja dengan sengatan dari input pengguna atau sumber lain. Tanpa menerapkan dampak emisi ini, pengguna harus bekerja dengan tipe ini dengan cara berikut:

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
}

atau untuk persimpangan:

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
}

Saya tidak berpikir bahwa memaksa pengguna untuk menggandakan kode dan menggunakan pemeran eksplisit, ketika itu dapat dengan mudah ditangani oleh kompiler bukanlah cara yang baik untuk dilakukan. Memancarkan dampak benar-benar sangat kecil dan dapat diprediksi, saya yakin itu tidak akan mengejutkan pengguna atau menyebabkan beberapa fitur disalahpahami atau sulit untuk menemukan bug, sementara menerapkan fitur ini tanpa memancarkan perubahan pasti AKAN.

Sebagai kesimpulan, saya ingin mengatakan bahwa secara sederhana tipe regex-validated adalah variabel cakupan dan tipe kompiler.

@DanielRosenwasser dan @alexanderbird ok, saya punya satu ide lagi untuk itu. Bagaimana dengan sintaks seperti ini:

const type Email = /email-regex/;

Dalam hal ini pengguna harus secara eksplisit mendefinisikan bahwa dia menginginkan ini sebagai type dan const , jadi sistem tipe aktual tidak memiliki perubahan emisi kecuali jika digunakan dengan pengubah tersebut. Tetapi jika digunakan dengannya, kami masih dapat menghindari banyak kesalahan, pemeran, dan duplikasi kode dengan menambahkan emisi yang sama seperti untuk:

const Email = /email-regex/;

Ini tampaknya lebih besar dari sekadar peningkatan untuk proposal ini, karena ini mungkin memungkinkan sesuatu seperti ini (contohnya dari proyek dengan Redux ):

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

diubah menjadi

export const type SOME_ACTION = 'SOME_ACTION';

Saya sudah mencoba menemukan beberapa saran serupa tetapi tidak berhasil. Jika itu bisa menjadi solusi dan jika Anda menyukai ide seperti itu, saya dapat menyiapkan Proposal Desain dan mengujinya.

@DanielRosenwasser , tentang masalah kedua Anda - Saya tidak berpikir itu akan pernah terjadi, karena dalam saran saya, kompiler menjalankan regex hanya untuk literal dan sepertinya seseorang tidak akan melakukan sesuatu seperti ini:

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

Bagaimanapun, kami dapat menguji berapa lama literal seharusnya untuk memengaruhi kinerja waktu nyata dan membuat beberapa heuristik yang akan memperingatkan pengguna jika kami tidak dapat memeriksanya saat dia menghadapi keadaan ini dalam beberapa skenario editor, tetapi kami akan memeriksanya ketika dia akan mengkompilasi proyek. Atau mungkin ada beberapa solusi lain.

Tentang pertanyaan ketiga, saya tidak yakin memahami semuanya dengan benar, tetapi tampaknya mesin regex harus dipilih tergantung pada target dari tsconfig jika mereka memiliki implementasi yang berbeda. Perlu beberapa penyelidikan lebih lanjut.

@DanielRosenwasser apakah ada pemikiran? Tentang proposal awal dan tentang proposal terakhir. Mungkin saya harus membuat gambaran yang lebih rinci tentang yang kedua, bukan?

@Igmat Proposal Anda membatasi validasi hanya untuk berguna dengan tipe string. Apa pendapat Anda tentang proposal @rylphs ? Ini akan memungkinkan validasi yang lebih umum untuk semua tipe primitif:

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

Namun saya menduga bahwa memperluas mekanisme ini di luar primitif ke tipe non-primitif akan terlalu banyak.
Satu hal, masalah yang diangkat oleh @DanielRosenwasser -- tentang berbagai implementasi mesin regex -- akan diperbesar: tergantung pada mesin Javascript di mana kompiler TypeScript berjalan, fungsi validasi mungkin bekerja secara berbeda.

@zspitz itu terlihat menjanjikan tetapi menurut saya itu dapat mempengaruhi kinerja kompiler terlalu banyak, karena fungsi tidak dibatasi oleh aturan apa pun dan itu akan memaksa TS untuk menghitung beberapa ekspresi yang terlalu rumit atau bahkan bergantung pada beberapa sumber daya yang tidak tersedia dalam waktu kompilasi.

@Igmat

karena fungsinya tidak dibatasi oleh aturan apa pun

Apakah Anda memiliki beberapa contoh spesifik dalam pikiran? Mungkin dimungkinkan untuk membatasi sintaks validasi ke subset TypeScript yang "aman"/waktu kompilasi yang diketahui.

bagaimana dengan membuat penjaga tipe yang ditentukan pengguna mendefinisikan tipe baru?

// 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 terlihat bagus, tapi bagaimana dengan tipe yang diperluas?

Apakah mereka benar-benar perlu diperpanjang? Apakah ada prinsip desain TypeScript yang menuntutnya? Jika tidak, saya lebih suka tipe nominal yang disarankan @disjukr daripada tidak sama sekali, meskipun tidak dapat diperpanjang.

Kami membutuhkan sesuatu yang cukup kreatif untuk mendapatkan perpanjangan IMHO - kami tidak dapat menentukan apakah satu tipe arbitrer (fungsi) adalah subset dari arbitrer tipe guard lainnya.

Kita bisa mendapatkan "perpanjangan" yang belum sempurna menggunakan mentalitas penegasan tipe (saya tidak mengatakan ini adalah sesuatu yang "cukup kreatif" - saya katakan inilah jeda sampai seseorang menemukan sesuatu yang cukup kreatif):

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
}

Yang mungkin berguna meskipun tidak terdengar. Tapi seperti yang saya katakan di awal, apakah kita mengharuskan itu dapat diperpanjang, atau dapatkah kita mengimplementasikannya seperti yang disarankan oleh @disjukr? (Jika yang terakhir, saya sarankan kita mengimplementasikannya dengan cara @disjukr yang tidak dapat diperpanjang.)

Agak di luar topik , balas komentar pertama
Untuk daftar yang dipisahkan koma, Anda harus menggunakan jangkar ^ dan $ (hal ini relevan dalam banyak kasus ketika Anda ingin memvalidasi beberapa string). Dan jangkar membantu menghindari pengulangan, misalnya regexp Anda akan menjadi /^((dog|cat|fish)(,|$))+$/

Izinkan tipe string menjadi ekspresi reguler /#[0-9]{6}/ dan izinkan tipe bersarang menjadi ekspresi reguler ${TColor} :

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

Hasil:

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

Kasus penggunaan: ada perpustakaan yang didedikasikan untuk menulis gaya CSS "type-safe" di TypeScript typestyle . Fungsionalitas yang diusulkan di atas akan sangat membantu, karena perpustakaan harus mengekspos metode yang akan digunakan saat runtime, tipe string regex yang diusulkan akan dapat mengetik kode cek pada waktu kompilasi dan memberikan pengembang intellisense yang hebat.

@DanielRosenwasser @alexanderbird @Igmat : IMO proposal ini akan mengubah permainan untuk TypeScript dan pengembangan web. Apa yang saat ini menghentikannya untuk diterapkan?

Saya setuju bahwa perpanjangan dan emisi tipe tidak boleh menghalangi fitur lainnya. Jika tidak ada jalur yang jelas pada aspek-aspek itu, terapkan nanti ketika ada.

Saya tiba di sini karena saya ingin memiliki tipe UUID dan bukan string, maka memiliki regex yang mendefinisikan string akan luar biasa dalam kasus ini + cara untuk memeriksa validitas tipe (contoh Email.test) juga akan membantu.

@skbergam Saya mencoba mengimplementasikannya sendiri sekali lagi. Tetapi proyek TS sangat besar dan saya juga punya pekerjaan, jadi hampir tidak ada kemajuan (saya hanya berhasil membuat tes untuk fitur baru ini). Jika seseorang memiliki lebih banyak pengalaman dengan memperluas TS, bantuan apa pun akan sangat dihargai ...

Menarik bahwa ini secara efektif membuat tipe nominal, karena kami tidak dapat membuat hubungan subtipe/penugasan apa pun antara dua regexp yang tidak identik

@RyanCavanaugh Sebelumnya @maiermic berkomentar

Sunting: Sepertinya masalah ini dapat diselesaikan dalam waktu polinomial (lihat Masalah Penyertaan untuk Ekspresi Reguler).

Tapi itu mungkin tidak cukup baik? Orang tentu berharap tidak ada banyak hubungan regexp, tetapi Anda tidak pernah tahu.

Mengenai pemeriksaan jenis, jika kami tidak suka menduplikasi regexps, dan typeof const tidak cukup baik (misalnya file .d.ts), bagaimana perasaan TS tentang valueof e , yang memancarkan nilai literal e iff e adalah literal, jika tidak, kesalahan (dan mengeluarkan sesuatu seperti undefined )?

@maxlk Juga di luar topik tetapi saya mengambil regex Anda dan memperbaikinya agar tidak cocok dengan koma tambahan pada input yang valid: /^((dog|cat|fish)(,(?=\b)|$))+$/ dengan tes https://regex101.com/r/AuyP3g/1. Ini menggunakan lookahead positif untuk karakter kata setelah koma, memaksa prior untuk memvalidasi ulang dengan cara KERING.

Hai!
Ini statusnya apa?
Apakah Anda akan menambahkan fitur ini dalam waktu dekat? Tidak dapat menemukan apa pun tentang ini di peta jalan.

@lgmat Bagaimana membatasi sintaks untuk fungsi panah satu baris, hanya menggunakan definisi yang tersedia di lib.d.ts ?

Apakah peningkatan luar biasa itu tersedia? Mungkin dalam rilis alpha setidaknya?

tipe yang divalidasi regex bagus untuk menulis tes, memvalidasi input hardcoded akan bagus.

+1. Kasus penggunaan kami sangat umum, kami harus memiliki format tanggal string seperti 'dd/mm/YYYY'.

meskipun seperti yang diusulkan itu akan menjadi fitur yang sangat keren, itu tidak memiliki potensi:

  • outputnya selalu menyengat (walaupun waktu kompilasi diperiksa), tidak ada cara untuk mendapatkan objek terstruktur
  • tata bahasa terbatas pada apa yang dapat dilakukan regexp, dan mereka tidak dapat berbuat banyak, masalah ekspresi reguler adalah bahwa mereka teratur, hasilnya adalah daftar, bukan pohon, mereka tidak dapat mengekspresikan level bersarang panjang yang sewenang-wenang
  • parser harus diekspresikan dalam tata bahasa TypeScript, yang terbatas dan tidak dapat diperluas

cara yang lebih baik adalah dengan meng-outsource parsing dan memancarkan ke plugin, seperti yang diusulkan di #21861, dengan cara ini semua hal di atas tidak menjadi masalah dengan harga kurva belajar yang lebih curam, tapi hei! pemeriksaan regexp dapat diimplementasikan di atasnya sehingga proposal asli masih berlaku, muncul oleh mesin yang lebih canggih

jadi seperti yang saya katakan, cara yang lebih umum adalah penyedia sintaks khusus untuk literal apa pun: #21861

contoh:

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 Bagaimana membatasi sintaks untuk fungsi panah satu baris, hanya menggunakan definisi yang tersedia di lib.d.ts ?

@zspitz itu akan membuat banyak orang tidak senang, seperti yang akan mereka lihat, bahwa itu mungkin, tetapi dilarang bagi mereka, pada dasarnya untuk keselamatan mereka.

Apakah peningkatan luar biasa itu tersedia? Mungkin dalam rilis alpha setidaknya?

Sejauh yang saya tahu ini masih membutuhkan proposal. @gtamas , @AndrewEastwood

Juga saya pikir #11152 akan mempengaruhi ini.

@Igmat Proposal Anda membatasi validasi hanya untuk berguna dengan tipe string. Apa pendapat Anda tentang proposal @rylphs ? Ini akan memungkinkan validasi yang lebih umum untuk semua tipe primitif:

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

Namun saya menduga bahwa memperluas mekanisme ini di luar primitif ke tipe non-primitif akan terlalu banyak.

Masalah utama yang saya lihat dengan ini adalah masalah keamanan, bayangkan beberapa kode berbahaya, yang akan menggunakan buffer untuk mengambil memori pengguna saat memeriksa jenisnya. Kita harus menerapkan banyak sandboxing di sekitar ini. Saya lebih suka melihat 2 solusi berbeda, satu untuk string dan satu untuk angka.

RegExp kebal terhadap itu untuk beberapa perluasan karena satu-satunya cara Anda dapat menggunakan ini secara jahat adalah dengan membuat beberapa ekspresi mundur . Meskipun demikian, beberapa pengguna mungkin melakukannya secara tidak sengaja, oleh karena itu, harus ada semacam perlindungan. Saya akan berpikir cara terbaik untuk melakukannya adalah timer.

Satu hal, masalah yang diangkat oleh @DanielRosenwasser -- tentang berbagai implementasi mesin regex -- akan diperbesar: tergantung pada mesin Javascript di mana kompiler TypeScript berjalan, fungsi validasi mungkin bekerja secara berbeda.

Itu benar, ini buruk, tetapi kita dapat menyelesaikannya dengan menentukan bagian "modern" dari regExp yang kita perlukan untuk basis kode kita. Ini akan default ke regexp normal (apakah itu ES3?), yang berfungsi di setiap node . Dan opsi untuk mengaktifkan flag baru dan melihat di belakang pernyataan.

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

Jika pengguna telah menonaktifkan tanda dengan tanda tingkat lanjut.

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

TypeScript tidak akan mengevaluasi RegExp lanjutan, jika tidak disuruh. Tetapi saya menyarankan bahwa itu harus memberikan peringatan, menjelaskan apa yang terjadi dan bagaimana mengaktifkan pemeriksaan RegExp lanjutan.

Jika pengguna telah mengaktifkan flag dengan flag lanjutan dan node-nya mendukungnya.

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

Jika pengguna telah mengaktifkan flag dengan flag lanjutan dan node-nya mendukungnya.

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

Saya pikir ini adalah cara yang masuk akal untuk melakukannya.
Tim pemrogram biasanya memiliki versi NodeJS yang sama atau dapat dengan mudah ditingkatkan karena semua basis kode mereka berfungsi untuk seseorang dengan versi yang lebih baru.
Pemrogram solo dapat beradaptasi dengan mudah dengan cepat,

Apa status masalah ini saat ini? Sangat disayangkan untuk melihat bahwa TypeScript memiliki potensi yang sangat besar dan lusinan proposal yang luar biasa, tetapi mereka tidak mendapatkan banyak perhatian dari para pengembang…

AFAIK proposal asli bagus terlepas dari gambaran Emit yang tidak boleh dan tidak terlalu dibutuhkan, jadi tidak boleh menghalangi proposal.

Masalah yang coba diatasi dapat diselesaikan dengan pengenalan literal regex (yang seharusnya tidak sulit, karena secara efektif setara dengan literal string dan angka) dan operator tipe patternof (mirip dengan typeof dan keyof ), yang akan mengambil tipe literal regex dan mengembalikan tipe _validated string_. Ini adalah bagaimana hal itu dapat digunakan:

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 Saya tidak memikirkan solusi seperti itu dengan operator tipe tambahan, ketika sedang mengerjakan proposal awal.

Saya suka pendekatan ini untuk menghilangkan dampak emisi yang disebabkan oleh tipe, meskipun ini tampaknya lebih bertele-tele.

Dan ini membawa saya ke ide bagaimana memperpanjang proposal ini untuk melewatkan penambahan kata kunci baru (seperti yang Anda sarankan) - IMO kami sudah memiliki jumlah yang cukup besar dan tidak memiliki dampak dari sistem tipe (seperti dalam proposal saya).

Ini akan mengambil 4 langkah:

  1. tambahkan regexp-validated string literal jenis:
    TypeScript type Email = /some-long-email-regex/;
  2. Mari kita ubah antarmuka RegExp di lib inti menjadi generik:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Ubah jenis infer untuk literal regex dalam kode aktual:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Tambahkan tipe helper menggunakan fitur conditional types , seperti InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Contoh penggunaan:

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 Keren. Proposal Anda terasa lebih alami untuk TypeScript dan membutuhkan lebih sedikit perubahan pada kompiler, itu mungkin hal yang baik. Satu-satunya keuntungan dari proposal saya adalah literal regex akan terasa sama dengan literal string dan angka, ini bisa membingungkan bagi sebagian orang:

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

Tapi saya pikir kesederhanaan proposal Anda lebih besar daripada kerugiannya.

Sunting: Saya tidak terlalu suka panjang ValidatedStringType<R> . Jika kita memutuskan untuk memanggil pola string yang divalidasi, kita bisa menggunakan PatternOf<R> . Saya tidak mengatakan bahwa tipe Anda membutuhkan waktu lebih lama untuk mengetik, kebanyakan orang hanya akan mengetik tiga huruf pertama dan menekan tab. Itu hanya memiliki dampak spagetifikasi kode yang lebih besar.

@Igmat Solusi Anda sangat baik dari titik pengembangan, tetapi seiring keterbacaan, akan jauh lebih baik untuk memiliki kemungkinan seperti yang diusulkan @m93a . Saya pikir itu bisa direpresentasikan secara internal dengan cara yang sama, tetapi harus disajikan kepada pengguna sesederhana mungkin.

@Akxe Saya tidak berpikir bahwa para pengembang akan suka menambahkan kata kunci lain yang hanya memiliki satu kasus penggunaan yang sangat spesifik.

@RyanCavanaugh Bisakah Anda memberi tahu kami pendapat Anda tentang ini? (Khususnya proposal asli dan empat komentar terakhir (tidak termasuk yang ini).) Terima kasih! :+1:

bagaimana dengan memiliki argumen umum untuk string, yang defaultnya adalah .* ?

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

string literal 'foo' dapat dianggap sebagai gula untuk string<foo>

Saya tidak terlalu suka panjang ValidatedStringType<R> . Jika kita memutuskan untuk memanggil string yang divalidasi _patterns_, kita bisa menggunakan PatternOf<R> bagaimanapun juga.

@m93a , IMO, dalam hal ini akan lebih baik untuk memanggil mereka PatternType<R> agar konsisten dengan InstanceType dan ReturnType helper yang sudah ada.

@amir-arad, menarik. Bagaimana tampilan interface RegExp dalam kasus ini?

@RyanCavanaugh saya bisa menulis ulang proposal asli dengan cara yang baru ditemukan jika itu akan membantu. Haruskah saya?

@amir-arad Sintaks yang Anda usulkan bertentangan dengan TypeScript lainnya. Sekarang Anda hanya dapat meneruskan tipe sebagai argumen umum, bukan ekspresi arbitrer. Sintaks yang Anda usulkan akan sangat membingungkan.

Pikirkan tipe generik seperti fungsi yang mengambil tipe dan mengembalikan tipe. Dua potongan kode berikut sangat dekat dalam arti dan sintaks:

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

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

Sintaks baru Anda seperti mengusulkan bahwa regex dalam JavaScript harus ditulis let all = String(.*) yang akan menjadi penyalahgunaan yang buruk dari sintaks panggilan fungsi. Oleh karena itu, menurut saya proposal Anda tidak masuk akal.

@m93a saran saya adalah untuk anotasi jenis, bukan javascript.

@Igmat dari atas kepala saya, bagaimana dengan:

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

@amir-arad, maaf, saya tidak dapat menambahkan detail yang lebih berharga ke saran Anda, tetapi pada pandangan pertama sepertinya perubahan yang sangat signifikan pada seluruh kompiler TS, karena string sangat primitif.

Meskipun saya tidak melihat masalah yang jelas, saya pikir proposal tersebut harus jauh lebih rinci dan mencakup banyak skenario yang ada ditambah pembenaran yang tepat dari tujuannya.
Proposal Anda menambahkan satu jenis dan mengubah satu jenis primitif, sedangkan milik saya hanya menambahkan satu jenis.

Sayangnya, saya tidak siap untuk mendedikasikan banyak waktu untuk membuat proposal untuk fitur tersebut (juga, Anda mungkin memperhatikan bahwa tidak setiap proposal di TS telah diterapkan tanpa penundaan yang signifikan), tetapi jika Anda mau mengerjakan ini, saya' akan dengan senang hati memberi Anda umpan balik saya jika diperlukan.

Jika tipe-regexp ini adalah ekspresi reguler nyata (bukan ekspresi reguler seperti Perl yang tidak reguler), kita dapat menerjemahkannya ke FSM deterministik dan menggunakan konstruksi produk kartesius untuk mendapatkan semua konjungsi dan disjungsi. Ekspresi reguler ditutup di bawah operasi boolean.

Juga jika tipe literal string tidak atomik, tetapi direpresentasikan sebagai daftar karakter waktu kompilasi, itu akan memungkinkan untuk mengimplementasikan semua operator di perpustakaan. Itu hanya akan sedikit memperburuk kinerja.

Sunting: Perbaiki kesalahan.

Mampir untuk mencatat bahwa Mithril benar -

  • Kait siklus hidup kami, oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove , dan onremove , memilikinya sendiri prototipe khusus.
  • Penangan acara pada vnodes DOM secara harfiah adalah apa pun yang dimulai dengan on , dan kami mendukung fungsi pendengar acara dan objek pendengar acara (dengan metode handleEvent ), menyelaraskan dengan addEventListener dan removeEventListener .
  • Kami mendukung kunci dan referensi yang sesuai.
  • Segala sesuatu yang lain diperlakukan sebagai atribut atau properti, tergantung pada keberadaannya di simpul DOM pendukung itu sendiri.

Jadi dengan regex-validated string type + type negation , kita bisa melakukan hal berikut untuk DOM vnodes:

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

    // Control attributes
    key: PropertyKey;
}

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

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

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

(Akan menyenangkan juga dapat mengekstrak grup dari regex seperti itu, tetapi saya tidak akan menahan nafas untuk itu.)

Sunting: Perjelas beberapa detail penting dalam proposal.
Sunting 2: Perbaiki bit teknis agar benar-benar akurat secara matematis.
Sunting 3: Tambahkan dukungan untuk pemeran umum serikat karakter tunggal

Berikut adalah proposal konkret untuk mencoba menyelesaikan ini dengan lebih layak: tipe literal templat.

Juga, saya merasa regexps penuh mungkin bukan ide yang baik, karena seharusnya cukup mudah untuk digabungkan dengan tipe lain. Mungkin ini mungkin lebih baik: tipe literal templat.

  • `value` - Ini secara harfiah setara dengan "value"
  • `value${"a" | "b"}` - Ini secara harfiah setara dengan "valuea" | "valueb"
  • `value${string}` - Ini secara fungsional setara dengan regexp /^value/ , tetapi "value" , "valuea" , dan "valueakjsfbf aflksfief fskdf d" semuanya dapat ditetapkan untuk itu.
  • `foo${string}bar` - Ini secara fungsional setara dengan regexp /^foo.*bar$/ , tetapi sedikit lebih mudah untuk dinormalisasi.
  • Tentu saja bisa ada banyak interpolasi. `foo${string}bar${string}baz` adalah tipe literal template yang valid.
  • Interpolasi harus diperluas string , dan tidak boleh rekursif. (Syarat kedua adalah karena alasan teknis.)
  • Tipe literal templat A dapat ditetapkan ke tipe literal templat B jika dan hanya jika kumpulan string yang dapat ditetapkan ke A adalah subset dari kumpulan string yang dapat ditetapkan ke B .

Selain di atas, tipe starof T akan ada, di mana T harus hanya terdiri dari tipe literal string karakter tunggal. string akan ada sebagai alias tipe starof (...) , di mana ... adalah gabungan dari semua literal string karakter UCS-2 tunggal dari U+0000 hingga U+FFFF, termasuk tunggal pengganti. Ini memungkinkan Anda menentukan tata bahasa lengkap untuk literal numerik ES base-10, misalnya:

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

Dan juga, metode bawaan tertentu dapat disesuaikan untuk mengembalikan tipe seperti itu:

  • Number.prototype.toString(base?) - Ini dapat mengembalikan tipe Numeric atau beberapa variannya untuk base diketahui secara statis.
  • +x , x | 0 , parseInt(x) , dan sejenisnya - Ketika x diketahui sebagai Numeric seperti yang didefinisikan di atas, tipe yang dihasilkan dapat disimpulkan dengan tepat sebagai tipe angka literal.

Dan akhirnya, Anda dapat mengekstrak grup yang cocok seperti: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . Ekstraksi template mengasumsikan itu selalu bekerja dengan nama lengkap, jadi Anda harus secara eksplisit menggunakan interpolasi ${string} untuk mencari penyertaan arbitrary. Ini tidak serakah, jadi ` "foo.bar.baz" extends ${infer T}.${infer U} ? [T, U] : never mengembalikan ["foo", "bar.baz"] , bukan ["foo.bar", "baz"] .


Dari sudut pandang teknis, ini jauh lebih layak untuk diterapkan daripada regexps mentah. Regexp JS bahkan tidak biasa - mereka menjadi peka konteks dengan referensi balik, dan melibatkan banyak kerumitan dalam bentuk Selama Anda memblokir rekursi dengan ini, tipe literal templat masing-masing menghasilkan satu bahasa reguler , yang sejalan sangat erat dengan teori yang mendasarinya (tetapi hanya mendukung sebagian darinya).

  • Bahasa kosong: ""
  • Serikat: "a" | "b"
  • Penggabungan: `${a}${b}`
  • Kleene star (sebagian): starof T ( T hanya dapat berisi satu karakter dan gabungan.)

Ini mungkin membuat subtipe string memeriksa subset dari skenario kasus terburuk masalah isomorfisme subgraf , tetapi ada beberapa faktor penebusan besar di sini:

  1. Kasus umum sejauh ini adalah penyatuan string kecil yang terbatas, sesuatu yang dapat Anda modelkan dengan pohon. Ini relatif jelas untuk dikerjakan. (Saya tidak menyarankan mencoba menggabungkannya sebagai tali, karena itu akan memperumit algoritma pencocokan di atas, tetapi tidak apa-apa untuk menormalkan penyatuan karakter tunggal dan serupa menjadi satu split + join.)

  2. Anda dapat memodelkan seluruh tipe terpadu sebagai grafik berarah, di mana:

    1. Serikat berbintang dari karakter tersebut adalah subgraf di mana simpul induk memiliki tepi untuk setiap karakter dan setiap simpul anak dari subgraf, dan setiap karakter memiliki tepi untuk semua karakter lain dan semua simpul anak dari subgraf.
    2. Sisa grafik memiliki struktur seperti pohon terarah yang mewakili semua kemungkinan lain.

    Menurut obrolan Math.SE yang saya ikuti secara singkat (dimulai kira-kira di sini ), saya menemukan bahwa grafik yang dihasilkan ini akan memiliki genus terbatas (yaitu dengan jumlah lompatan terbatas pada tepi lain*) dan, tidak ada starof jenis, gelar terbatas. Ini berarti kesetaraan tipe menguranginya menjadi masalah waktu polinomial dan dengan asumsi Anda menormalkan serikat pekerja, itu juga tidak super lambat karena hanya agak lebih cepat daripada kesetaraan pohon. Saya sangat menduga kasus umum untuk seluruh proposal ini (bagian dari masalah isomorfisme subgraf) juga waktu polinomial dengan koefisien yang masuk akal. (Artikel Wikipedia yang ditautkan di atas memiliki beberapa contoh di bagian "Algoritma" dan referensi di mana casing khusus mungkin berlaku.)


  3. Tak satu pun dari kunci ini cenderung besar , sehingga sebagian besar biaya runtime aktual di sini diamortisasi dalam praktiknya oleh hal-hal lain. Selama itu cepat untuk kunci kecil, itu cukup bagus.


  4. Semua subgraf yang akan dibandingkan memiliki setidaknya satu simpul: simpul akar. (Ini mewakili awal dari string.) Jadi ini akan secara dramatis mengurangi ruang masalah dengan sendirinya dan menjamin pemeriksaan waktu polinomial.


Dan tentu saja, persilangan antara jenis-jenis tersebut tidak sepele , tetapi saya merasa ada faktor penebusan yang serupa hanya karena pembatasan di atas. Secara khusus, pembatasan terakhir membuatnya jelas polinomial-waktu untuk dilakukan.

* Secara matematis, genus didefinisikan sedikit berlawanan dengan intuisi bagi kami para pemrogram (jumlah minimum lubang yang Anda perlukan untuk menggambar grafik tanpa lompatan), tetapi genus terbatas (jumlah lubang terbatas) menyiratkan jumlah lompatan terbatas .

Menggunakan proposal konkret ini, beginilah contoh saya dari komentar ini diterjemahkan:

// 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: Ini juga akan memungkinkan benar mengetik 90% dari Lodash ini _.get metode dan metode terkait menggunakan singkatan properti, seperti yang _.property(path) metode dan yang _.map(coll, path) steno. Mungkin ada beberapa yang lain yang tidak saya pikirkan juga, tapi itu mungkin yang terbesar yang bisa saya pikirkan. (Saya akan menyerahkan implementasi tipe itu sebagai latihan kepada pembaca, tetapi saya dapat meyakinkan Anda bahwa itu mungkin dengan kombinasi itu dan trik tipe kondisional biasa dengan catatan yang segera diindeks, seperti {0: ..., 1: ...}[Path extends "" ? 0 : 1] , untuk memproses string jalur statis.)

Rekomendasi saya adalah kami memfokuskan upaya kami pada penerapan penyedia tipe, yang dapat digunakan untuk mengimplementasikan tipe regex.

Mengapa penyedia tipe alih-alih menerapkan tipe regex secara langsung? Karena

  1. Ini adalah solusi yang lebih umum yang menambahkan banyak kemungkinan baru ke TypeScript sehingga memudahkan untuk mendapatkan dukungan dari kelompok pengembang yang lebih luas di luar mereka yang melihat nilai dalam tipe string regex.
  2. Pemilik repo TypeScript tampaknya terbuka untuk ide ini, dan sedang menunggu proposal yang tepat. Lihat #3136

F# memiliki penyedia tipe regex open source.

Beberapa info tentang penyedia jenis: https://link.medium.com/0wS7vgaDQV

Orang dapat membayangkan bahwa setelah penyedia tipe diimplementasikan dan penyedia tipe regex diimplementasikan sebagai perpustakaan sumber terbuka, orang akan menggunakannya seperti ini:

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 Saya tidak yakin itu cara yang benar, setidaknya tidak untuk permintaan ini.

  • TypeScript diketik secara struktural, tidak diketik secara nominal, dan untuk manipulasi string literal, saya ingin mempertahankan semangat struktural itu. Penyedia tipe seperti itu akan membuat subtipe string nominal di mana RegexProvider</^foo$/> tidak akan diperlakukan sebagai setara dengan "foo" , tetapi subtipe nominalnya. Selanjutnya, RegexProvider</^foo$/> dan RegexProvider</^fo{2}$/> akan diperlakukan sebagai dua jenis yang berbeda, dan itu adalah sesuatu yang saya tidak suka. Proposal saya malah langsung terintegrasi dengan string pada intinya, secara langsung diinformasikan oleh teori pengenalan bahasa formal untuk memastikannya cocok secara alami.
  • Dengan milik saya, Anda tidak hanya dapat menggabungkan string, tetapi mengekstrak bagian dari string melalui Key extends `on${infer K}` ? K : never atau bahkan Key extends `${Prefix}${infer Rest}` ? Rest : never . Penyedia jenis tidak menawarkan fungsi ini, dan tidak ada cara yang jelas bagaimana seharusnya jika fungsi tersebut ditambahkan.
  • Milik saya jauh lebih sederhana pada tingkat konseptual: Saya hanya menyarankan agar kita menambahkan tipe rangkaian string dan, untuk RHS tipe kondisional, kemampuan untuk mengekstrak kebalikannya. Saya juga mengusulkan agar itu terintegrasi dengan string itu sendiri untuk menggantikan regexp /.*/ . Itu tidak memerlukan perubahan API, dan selain dari dua bagian yang secara teoritis kompleks yang sebagian besar dipisahkan dari basis kode lainnya, menghitung apakah tipe literal templat dapat ditetapkan ke yang lain dan mengekstraksi sepotong dari string, serupa, jika tidak lebih sederhana , untuk melaksanakan.

BTW, proposal saya masih bisa mengetik contoh PhoneNumber juga. Ini sedikit lebih bertele-tele, tetapi saya mencoba memodelkan data yang sudah ada di TS, bukan data yang ada di tempat lain (untuk apa penyedia tipe F # paling berguna). (Perlu dicatat ini secara teknis akan meluas ke daftar lengkap nomor telepon yang mungkin di sini.)

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$/> dan RegexProvider^fo{2}$/> akan diperlakukan sebagai dua tipe berbeda

Penyedia tipe mungkin memerlukan penerapan beberapa metode equals atau compare , sehingga pembuat penyedia tipe dari penyedia tipe regex dapat menentukan bahwa kedua kasus di atas adalah tipe yang setara. Penulis penyedia tipe dapat menerapkan pengetikan struktural atau nominal sesuka mereka.

Mungkin dimungkinkan untuk mengimplementasikan tipe literal string Anda sebagai penyedia tipe juga. Saya tidak berpikir sintaksnya bisa sama, tetapi Anda bisa mendekati penyedia tipe yang menerima sejumlah variabel argumen.

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 Tetapi apakah tipe "123-456-7890" dapat ditetapkan untuk tipe Anda? (Jika demikian, itu akan menyulitkan pelaksanaan dan memperlambat checker yang banyak.)

Semi terkait dengan pembahasan yang ada, bagaimana jika jenisnya tidak panjang tetap (seperti nomor telepon)? Satu situasi di mana saya ingin menggunakan ini baru-baru ini adalah untuk menyimpan nama ruangan, dengan format thread_{number} .

Regex yang cocok dengan nilai seperti itu adalah thread_[1-9]\d* . Dengan apa yang sedang diusulkan, tampaknya tidak layak (atau bahkan mungkin) untuk mencocokkan format seperti itu. Bagian numerik dari nilai bisa _any_ panjang lebih besar dari nol dalam situasi ini.

@jhpratt Saya merevisi proposal saya untuk mengakomodasi itu, dalam bentuk starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9") /^\d*$/ , karena hanya membutuhkan sedikit perubahan. Ini mengoptimalkan dengan cara yang sama string mengoptimalkan sebagai /^[\u0000-\uFFFF]*$/ , jadi saya memutuskan untuk melanjutkan dan menggeneralisasi itu.

Saya tidak ingin memperpanjang starof lebih dari itu, seperti menerima serikat non-rekursif sewenang-wenang, karena masalah kompleksitas komputasi: memverifikasi apakah dua ekspresi reguler arbitrer* setara dapat dilakukan dalam ruang polinomial atau sangat lambat dalam praktiknya dan AFAICT Anda tidak dapat memiliki keduanya. Tambahkan dukungan untuk kuadrat (seperti a{2} ), dan pada dasarnya tidak layak (kompleksitas eksponensial) . Ini hanya untuk kesetaraan, dan memeriksa apakah regexp cocok dengan subset string yang cocok dengan regexp lain, yang diperlukan untuk memeriksa penetapan, jelas akan menjadi lebih rumit.

* Ekspresi reguler dalam arti matematika: Saya hanya menyertakan karakter tunggal, () , (ab) , (a|b) , dan (a*) , di mana a dan b (berpotensi berbeda) masing-masing anggota daftar ini.

Ini mungkin pertanyaan bodoh, tapi... mengapa tidak cukup mudah, jika cukup terbatas, untuk mendukung fungsi validasi (baik lambda atau bernama)?

Misalnya, kita menggunakan ":" untuk menunjukkan bahwa elemen berikutnya adalah validator (gantikan apa pun yang Anda inginkan untuk ":" jika Anda memiliki pendapat tentang ini):

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

Sebagai permulaan awal, TypeScript hanya dapat menerima fungsi validasi yang:

  • memiliki satu argumen, yang diperlukan/diasumsikan dari tipe "dasar"
  • hanya variabel referensi yang didefinisikan dalam fungsi validator
  • mengembalikan nilai (yang akan dipaksa untuk bool dalam proses validasi)

Kondisi di atas tampaknya mudah untuk diverifikasi oleh kompiler TypeScript, dan setelah kondisi tersebut diasumsikan, sebagian besar kerumitan implementasi akan hilang.

Selain itu, jika perlu untuk membatasi cakupan awal ke ukuran yang dapat dikelola:

  • fungsi validasi hanya dapat ditambahkan ke subset tipe asli (string, angka)

Saya tidak berpikir pembatasan terakhir ini akan terlalu diperlukan, tetapi jika ada pertanyaan apakah itu akan terjadi, saya juga tidak berpikir perlu menghabiskan banyak waktu untuk memperdebatkannya, karena solusi dengan batasan di atas masih akan memecahkan sejumlah besar kasus penggunaan dunia nyata. Selain itu, saya melihat sedikit kelemahan dari batasan di atas karena melonggarkannya nanti akan menjadi ekstensi yang sederhana dan alami yang tidak memerlukan perubahan dalam sintaks dasar dan hanya akan memperluas luasnya dukungan bahasa oleh kompiler.

@mewalig Itu berarti bahwa sesuatu yang terlihat seperti fungsi runtime sebenarnya tidak akan dieksekusi pada saat runtime, tetapi pada waktu kompilasi (dan setiap kali Anda ingin memeriksa asignability). Fungsi-fungsi ini tidak dapat mengakses apa pun dari runtime (variabel, fungsi) yang akan terasa sangat canggung.

Plus, Anda biasanya tidak ingin kompiler menjalankan apa pun yang Anda lakukan, terutama fungsi yang dioptimalkan dengan buruk atau while(true){} berbahaya. Jika Anda ingin pemrograman meta, Anda harus mendesainnya dengan cerdas. Membiarkan kode runtime secara acak berjalan pada waktu kompilasi akan menjadi "cara PHP" untuk melakukannya.

Akhirnya, sintaks yang Anda usulkan mengubah pola yang biasa

let runtime: types = runtime;

(yaitu jenis setelah titik dua) dalam ke luar, secara efektif menjadi

type types = types: runtime;

yang mengerikan. Jadi terima kasih atas proposal Anda, tapi itu jelas ide yang buruk.

Fungsi-fungsi ini tidak dapat mengakses apa pun dari runtime (variabel, fungsi) yang akan terasa sangat canggung.

Tentu saja mereka bisa , jika kompiler memiliki runtime ECMAScript yang tersedia untuknya ( tsc tidak, BTW!). Anda jelas memiliki masalah ambiguitas dengan semantik waktu kompilasi misalnya fetch() vs. semantik runtime, tapi itulah iterasinya.

Membiarkan kode runtime secara acak berjalan pada waktu kompilasi akan menjadi "cara PHP" untuk melakukannya.

Ini sangat mirip dengan fungsi C++ constexpr , yang baik-baik saja. Solusinya adalah mengatakan bahwa constexpr hanya dapat menggunakan constexpr , tetapi semuanya dapat menggunakan constexpr . Kemudian Anda dapat memiliki constexpr -versi setara sistem file untuk sistem file waktu kompilasi yang bisa sangat kuat.

Sintaksnya juga terlihat baik-baik saja bagi saya: LHS adalah tipe, tentu saja RHS juga sejenis. Masalah saya lebih tentang bagaimana Anda akan menulis tipe melewati tipe "dasar", tapi itu semua juga bisa dipecahkan.

Jadi terima kasih atas proposal Anda, tapi itu jelas ide yang buruk.

Ini mungkin berakhir menjadi ide yang buruk, tetapi untuk saat ini saya hanya melihat ide yang sangat kurang spesifik yang kemungkinan akan membutuhkan menyimpang terlalu jauh dari tujuan TypeScript. Itu tidak berarti bahwa mungkin tidak ada ide bagus yang serupa dengan itu!

Diskusi tentang fitur ini tampaknya berhenti untuk saat ini ( PR ditutup dan menurut tim catatan Desain _tidak ingin melakukan ini sampai kita memiliki tipe nominal dan tanda tangan indeks umum, dan kita harus tahu seperti apa tampilannya._).

Bagaimanapun, saya ingin mengusulkan ekstensi hipotetis lain untuk PR saat ini yang akan mendukung ekstraksi pola regex ( @isiahmeadows mempresentasikan proposalnya sendiri, tetapi sejujurnya saya tidak dapat membungkusnya sekarang ...).

Saya sangat menyukai PR saat ini dan akan mendasarkan proposal saya pada hal itu. Saya ingin mengusulkan sintaks berdasarkan inferensi argumen tipe generik yang kami miliki untuk fungsi (dan tipe kondisional dengan kata kunci infer ). Hanya karena orang sudah memiliki intuisi bahwa dalam fungsi generik Anda dapat "mengekstrak" tipe dari objek literal yang diteruskan.

Misalnya kita memiliki tipe ini.

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

dan kita dapat menggunakan tipe ini untuk menguji tipe literal

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

Namun, ketika kita menggunakan tipe Regex dalam parameter fungsi, kita dapat menggunakan sintaks kurung sudut yang berarti bahwa kita ingin menyimpulkan string yang cocok. Sebagai contoh

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

perhatikan bahwa tipe kesimpulan dari res1 adalah ["foo", "bar"]

Apakah itu berguna?

  1. Ember.js/lodash dapatkan fungsi

Anda dapat menerapkan pengambil "jalur string" tipe-aman sehingga kode ini akan berfungsi:

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

Tetapi mungkin perlu untuk menyelesaikan ini jika kita ingin menghindari banyak kelebihan untuk jumlah maksimum tetap dari "kedalaman" get .

  1. Gunakan parameter yang diekstraksi dalam tipe yang dipetakan.

Misalnya jika kita dapat melakukan sesuatu seperti ini https://github.com/Microsoft/TypeScript/issues/12754 . Kemudian kita dapat memiliki kemungkinan untuk membalikkan fungsi (menghapus beberapa awalan/akhiran dari semua properti dari tipe yang diberikan). Yang ini mungkin perlu memperkenalkan beberapa bentuk sintaks yang dipetakan yang lebih umum untuk memilih kunci baru untuk properti (misalnya sintaks seperti { [ StripAsyncSuffix<P> for P in K ] : T[P] } , seseorang sudah mengusulkan sesuatu seperti itu)

Mungkin akan ada kasus penggunaan lain juga. Tapi saya kira sebagian besar akan cocok dengan dua tipe ini (1. mencari tahu tipe yang tepat berdasarkan string literal yang disediakan, 2. mengubah nama properti dari tipe input menjadi nama properti baru dari tipe yang ditentukan baru)

Ini adalah sesuatu yang bisa kita lakukan.

Saat ini saya sedang membangun aturan lint khusus agar dapat memvalidasi url - meskipun, ini akan jauh lebih mudah jika kita dapat menentukan params opsional - yang memerlukan regex agar dapat memvalidasi id kita

Secara umum, ini akan memberi kami lebih banyak kekuatan untuk menegaskan validitas alat peraga di seluruh basis kode kami

Apakah ada pergerakan pada penyedia tipe, literal string templat, atau saran lainnya? Ini akan menjadi alat yang hebat.

Solusi saya untuk saat ini adalah dengan menggunakan antarmuka penanda seperti ini .

interface TickerSymbol extends String {}

Satu-satunya masalah adalah ketika saya ingin menggunakannya sebagai kunci indeks, saya harus melemparkannya ke 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

Namun, JavaScript tampaknya baik-baik saja dengan tipe indeks String (dengan huruf kapital 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 Saya punya proposal di sini , dan proposal terpisah dibuat pada akhir 2016 , jadi bisakah label untuk ini diperbarui?

Kami telah meninjau proposal di atas dan memiliki beberapa pertanyaan dan komentar.

Aspek Proposal yang Bermasalah sejauh ini

Jenis Membuat Emit

Kami berkomitmen untuk menjaga sistem tipe terhapus sepenuhnya, sehingga proposal yang memerlukan alias tipe untuk menghasilkan kode yang dipancarkan berada di luar cakupan. Saya akan menyoroti beberapa contoh di utas ini di mana ini mungkin terjadi dengan cara yang tidak jelas:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -220180091 - membuat fungsi dan tipe secara bersamaan

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - juga melakukan ini

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

Saya akan mengulangi: ini adalah non-starter . Jenis dalam TypeScript dapat dikomposisi dan memancarkan JS dari jenis tidak dimungkinkan di dunia ini. Proposal terlama hingga saat ini memiliki tipe emisi-dari-luas; ini tidak bisa diterapkan. Misalnya, ini akan membutuhkan emisi terarah tipe ekstensif:

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

Larangan di Persimpangan

Sebenarnya tipe umum dan tipe yang divalidasi regex sangat berbeda, jadi kita perlu aturan bagaimana menangani penyatuan dan persimpangan mereka dengan benar.

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

TypeScript tidak dapat membuat kesalahan pada instantiasi persimpangan, jadi ini tidak akan menjadi bagian dari desain akhir apa pun.

Ergonomi

Secara keseluruhan takeaway kami yang paling menonjol adalah bahwa kami menginginkan sesuatu di mana Anda tidak menulis RegExp yang sama dua kali (sekali di ruang nilai, sekali di ruang tipe).

Mengingat kekhawatiran di atas tentang jenis emisi, solusi paling realistis adalah Anda akan menulis ekspresi dalam ruang nilai:

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

Anda masih bisa menulis RegExp di ruang tipe, tentu saja, tetapi tidak akan ada validasi runtime yang tersedia dan penggunaan nonliteral apa pun akan memerlukan pengujian ulang atau pernyataan:

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

Pengumpulan dan Klarifikasi Kasus Penggunaan

Untuk jenis tipe baru, idealnya kami ingin melihat beberapa contoh di mana:

  • Masalah yang sedang dipecahkan tidak memiliki
  • Masalahnya terjadi dengan frekuensi yang berarti dalam basis kode nyata
  • Solusi yang diusulkan memecahkan masalah itu dengan baik

Validasi Waktu Kompilasi dari Literal

Utas ini menyiratkan berbagai macam kasus penggunaan; contoh konkret lebih jarang. Masalahnya, banyak dari contoh ini tampaknya tidak lengkap - mereka menggunakan RegExp yang akan menolak input yang valid.

  • Warna font - AFAIK apa pun yang menerima warna hex juga menerima misalnya "putih" atau "biru langit". Ini juga salah menolak sintaks rgb(255, 0, 0) .
  • SSN, Zip, dll - OK, tetapi mengapa ada SSN atau Kode Pos literal dalam kode Anda? Apakah ini benar-benar kebutuhan untuk tipe nominal? Apa yang terjadi jika Anda memiliki subkelas string yang tidak dapat dijelaskan secara akurat oleh RegExp? Lihat "Proposal yang bersaing"

    • Integer - salah menolak "3e5"

    • Email - Ini biasanya dianggap sebagai ide yang buruk . Sekali lagi, ada literal string alamat email dalam kode Anda ?

    • Spesifikasi Perbatasan CSS - Saya percaya bahwa perpustakaan mandiri dapat memberikan RegEx yang akurat untuk menggambarkan DSL yang didukungnya sendiri

    • Menulis tes - di sinilah input hardcode masuk akal, meskipun ini hampir merupakan tandingan karena kode pengujian Anda mungkin harus memberikan banyak input yang tidak valid

    • Format tanggal - bagaimana/mengapa? Date memiliki konstruktor untuk ini; jika input berasal dari luar runtime maka Anda hanya ingin tipe nominal

    • URI - Anda dapat membayangkan bahwa fetch akan menentukan host untuk tidak bersama http(s?):

TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

Satu kekhawatiran adalah "precisionitis" - apa yang terjadi ketika seseorang dengan membantu muncul ke PastiTyped dan menambahkan tipe RegExp ke setiap fungsi di perpustakaan, sehingga melanggar setiap permintaan nonliteral? Lebih buruk lagi, pembuat file definisi harus benar -
Sepertinya ini dengan cepat menempatkan kita di jalan menuju situasi Menara Babel di mana setiap perpustakaan memiliki versi mereka sendiri tentang apa yang memenuhi syarat sebagai URL, apa yang memenuhi syarat sebagai nama host, apa yang memenuhi syarat sebagai email, dll, dan siapa pun yang menghubungkan dua perpustakaan harus memasukkan pernyataan tipe atau menyalin regex di sekitar untuk memenuhi kompiler.

Penegakan Pemeriksaan Runtime

Ada beberapa diskusi pemeriksaan di mana kami ingin memastikan bahwa argumen fungsi telah divalidasi oleh regex sebelumnya, seperti fn di bagian Ergonomi sebelumnya. Ini tampaknya mudah dan berharga, jika RegEx yang perlu diuji sudah terkenal. Itu adalah "jika" yang besar - dalam ingatan saya, saya tidak dapat mengingat satu perpustakaan pun yang menyediakan regex validasi. Ini mungkin menyediakan fungsi validasi - tetapi ini menyiratkan bahwa fitur yang akan disediakan adalah tipe nominal atau yang diberi tag, bukan tipe ekspresi reguler.

Kontra-bukti untuk penilaian ini disambut.

Kunci Properti / Pengindeks String Regex

Beberapa perpustakaan memperlakukan objek sesuai dengan nama properti. Misalnya, di React kami ingin menerapkan tipe ke prop apa pun yang namanya dimulai dengan aria- :

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

Ini secara efektif merupakan konsep ortogonal (kita dapat menambahkan tipe Regex tanpa menambahkan kunci properti Regex, dan sebaliknya).

TODO (saya atau siapa pun): Buka masalah terpisah untuk ini.

Proposal Bersaing

Jenis Nominal atau Tagged

Katakanlah kita memiliki beberapa jenis nominal/tag:

type ZipCode = make_unique_type string;

Anda kemudian dapat menulis fungsi

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

Pada titik ini, apakah Anda benar-benar membutuhkan tipe RegExp? Lihat bagian pemeriksaan "waktu kompilasi" untuk lebih banyak pemikiran.

Sebaliknya, katakanlah kita memiliki tipe RegExp dan bukan tipe nominal. Menjadi sangat tergoda untuk memulai (ab) menggunakannya untuk skenario non-validasi:

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

Hal yang umum di utas adalah bahwa regex ini akan membantu memvalidasi kode pengujian, karena meskipun dalam skenario produksi kode akan berjalan melawan string yang disediakan runtime daripada literal hardcoded, Anda masih menginginkan beberapa validasi bahwa string pengujian Anda adalah " benar". Namun, ini tampaknya menjadi argumen untuk string nominal/tag/bermerek, karena Anda akan menulis fungsi validasi dengan cara apa pun, dan manfaat tes adalah Anda tahu mereka berjalan secara mendalam (sehingga kesalahan apa pun dalam input pengujian akan ditandai di awal siklus pengembangan).

Non-Masalah

Kami membahas aspek-aspek berikut dan menganggapnya bukan pemblokir

Kemampuan Tuan Rumah

Runtime yang lebih baru mendukung lebih banyak sintaks RegExp daripada runtime yang lebih lama. Tergantung di mana kompiler TypeScript berjalan, kode tertentu mungkin valid atau tidak valid sesuai dengan kemampuan runtime untuk mengurai fitur RegExp yang lebih baru. Dalam praktiknya, sebagian besar fitur RegExp baru cukup esoteris atau terkait dengan pencocokan grup, yang tampaknya tidak selaras dengan sebagian besar kasus penggunaan di sini.

Pertunjukan

RegExes dapat melakukan jumlah pekerjaan yang tidak terbatas dan pencocokan dengan string besar dapat melakukan pekerjaan dalam jumlah besar secara sewenang-wenang. Pengguna sudah dapat DOS sendiri melalui cara lain, dan tidak mungkin untuk menulis RegExp yang tidak efisien dan berbahaya.

Subtipe ( /\d*/ -> /.*/ ?), Persatuan, Persimpangan, dan Tidak Dapat Dihuni

Secara teori /\d+/ adalah subtipe yang dapat diketahui dari /.+/ . Seharusnya ada algoritma untuk menentukan apakah satu RegExp cocok dengan subset murni dari yang lain (di bawah batasan tertentu), tetapi jelas akan memerlukan penguraian ekspresi. Dalam praktiknya, kami 100% setuju dengan RegExpes yang tidak membentuk hubungan subtipe implisit berdasarkan kecocokannya; ini bahkan mungkin lebih disukai.

Operasi Union dan Intersection akan bekerja "di luar kotak" selama hubungan penugasan didefinisikan dengan benar.

Di TypeScript, ketika dua tipe primitif "bertabrakan" di persimpangan, mereka berkurang menjadi never . Ketika dua RegExpes berpotongan, kami hanya akan menyimpannya sebagai /a/ & /b/ daripada mencoba menghasilkan RegExp baru yang cocok dengan persimpangan dua ekspresi. Tidak akan ada pengurangan menjadi never kita memerlukan algoritma untuk membuktikan bahwa tidak ada string yang dapat memuaskan kedua sisi (ini adalah masalah paralel dengan yang dijelaskan sebelumnya re: subtyping).

Langkah selanjutnya

Untuk meringkas, langkah selanjutnya adalah:

  • Ajukan masalah terpisah untuk kunci properti bernama Regex AKA pengindeks string regex
  • Dapatkan kasus penggunaan yang konkret dan masuk akal untuk validasi waktu kompilasi dari literal string

    • Contoh: Mengidentifikasi fungsi-fungsi di PastiTyped atau perpustakaan lain yang akan sangat diuntungkan dari ini

  • Pahami apakah tipe nominal/tag/merek adalah solusi yang lebih fleksibel dan dapat diterapkan secara luas untuk validasi non-literal
  • Identifikasi perpustakaan yang sudah menyediakan RegEx validasi

Kasus penggunaan: Hyperscript (https://github.com/hyperhype/hyperscript) seperti fungsi
Fungsi hyperscript biasanya disebut seperti h('div#some-id')
Pencocokan pola regex-ish akan memungkinkan untuk menentukan jenis pengembalian h yang akan menjadi HTMLDivElement dalam contoh kasus.

Jika sistem tipe dapat menambahkan literal string, maka pada dasarnya semua properti CSS dapat menjadi tipe-safe

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

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

Pemilih CSS juga dapat divalidasi ( element.class#id - valid, div#.name - tidak valid)

Jika menangkap grup akan berhasil (entah bagaimana) maka metode get Lodash dapat menjadi tipe-safe

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

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

Ini bisa menjadi hal juga:

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

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

Use case: Hyperscript (hyperhype/hyperscript) seperti fungsi

Seperti apa tampilan regex itu, atau validasi apa yang akan diberikannya? Apakah ini untuk kelebihan fungsi berbasis regex?

FWIW Perpustakaan menerima nama tag namespace dan juga berfungsi pada nama tag arbitrer

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

Itu juga menerima pencampuran nilai kelas dan id yang tidak terbatas

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

Pemilih CSS juga dapat divalidasi

Pemilih CSS tidak dapat divalidasi oleh ekspresi reguler

Seperti apa tampilan regex itu, atau validasi apa yang akan diberikannya? Apakah ini untuk kelebihan fungsi berbasis regex?

Bukan OP, tapi saya kira, ya, sesuatu seperti kelebihan HTMLDocument#createElement() , misalnya:

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

Saya yakin RE tidak lengkap. Perhatikan bahwa ini adalah kasus khusus memvalidasi pemilih CSS, yang digunakan dalam banyak konteks secara teratur. Misalnya, tidak masalah jika HTMLDocument.querySelector() mengembalikan HTMLElement sebagai fallback jika Anda menggunakan pemilih yang kompleks.

Saya ingin tahu apakah ada contoh non-overloading yang layak dan berguna.

TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

Kasus penggunaan saya adalah yang saya jelaskan dalam komentar ini di perpustakaan CCXT di mana saya memiliki string yang mewakili TickerSymbol s. Saya tidak terlalu peduli jika mereka diperiksa untuk pola regex, tetapi saya ingin mereka diperlakukan sebagai sub-tipe string jadi saya mendapatkan tugas yang lebih ketat, pemeriksaan tipe parameter, dll. Saya menemukannya sangat berguna ketika saya melakukan pemrograman fungsional, dengan itu saya dapat dengan mudah melacak TickerSymbols, Mata Uang, Aset, dll pada waktu kompilasi di mana pada saat run-time mereka hanyalah string normal.

@omidkrad Sepertinya Anda membutuhkan tipe nominal , bukan tipe yang divalidasi regex.

@ m93a Dalam kasus saya, saya akan baik-baik saja dengan tipe nominal, tetapi untuk kasus penggunaan yang sama, Anda dapat menggunakan tipe yang divalidasi regex untuk pemeriksaan tipe yang lebih ketat dan mendokumentasikan sendiri tipe string.

Pemilih CSS juga dapat divalidasi

Pemilih CSS tidak dapat divalidasi oleh ekspresi reguler

Nah, jika regexp memungkinkan kita untuk menyatukannya, kita bisa menyalin regex CSS..., kan?

Model Objek Ketik CSS (draf)

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

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

Berpotensi mengurangi keinginan untuk menggunakan model CSS yang diketik dengan string.

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

@RyanCavanaugh Khususnya untuk Mithril, nama tag diekstraksi melalui grup tangkap di ^([^#\.\[\]]+) (default ke "div" ), tetapi mencocokkan ^(${htmlTagNames.join("|")}) akan cukup untuk tujuan kita. Jadi menggunakan proposal saya , ini akan cukup untuk tujuan saya:

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

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

Untuk event dan atribut, kita bisa beralih ke tipe tanah yang pernah dinegasikan ini:

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, integrasi tanpa batas dan penghindaran kerumitan ini adalah mengapa saya masih lebih memilih proposal saya daripada regexps literal.


Saya tahu tidak ada cara untuk melakukan ini dengan tipe regexp murni. Saya ingin menunjukkan hal itu.

TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

bent memiliki tipe pengembalian yang berbeda berdasarkan apa yang diberikan sebagai string yang menggambarkan tipe respons yang diharapkan, misalnya

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

Itu juga menerima beberapa argumen lain, seperti metode dan url sebagai string, tetapi ini dapat muncul di posisi apa pun, jadi jika kami mencoba menggunakan serikat pekerja untuk menggambarkan semua tipe pengembalian ( 'json' | 'buffer' | 'string' ), ini malah akan menjadi bodoh menjadi hanya string bila digabungkan dengan jenis url dan metode dalam gabungan, artinya kita tidak dapat secara otomatis menyimpulkan jenis pengembalian berdasarkan jenis yang diberikan pada panggilan pertama.

@Ovyerus bagaimana tipe regex membantu Anda di sana? Apa yang Anda harapkan untuk ditulis? Anda dapat memodelkan sesuatu yang mirip dengan perilaku bengkok dengan kelebihan beban atau tipe kondisional.

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 saya tidak jelas maaf, saya yakin masalah saya lebih pada pencocokan http(s): di awal string untuk mendeteksi URL dasar.

Tanda tangan Bent lebih mirip dengan

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

Namun memiliki BaseUrl sebagai string menyerap HttpMethods dan mengembalikan serikat tipe, yang berakhir hanya sebagai string . Memilikinya hanya sebagai string juga "tidak benar" cocok dengan cara kerja bengkok, karena ia memeriksa keberadaan ^http: atau ^https: untuk menentukan apa yang harus digunakan sebagai url dasar.

Jika kami memiliki tipe regex, saya dapat mendefinisikan BaseUrl sebagai type BaseUrl = /^https?:/ , dan ini idealnya akan memverifikasi string yang bukan metode HTTP atau penyandian respons, serta tidak menyerapnya ke dalam string Tipe.

Persis, saya juga sama.

--
Prokop Simek

Pada 20 Oktober 2019 pukul 03:23:30, Michael Mitchell ([email protected])
menulis:

Oh saya tidak jelas maaf saya yakin masalah saya lebih sejalan
pencocokan http(s): di awal string untuk mendeteksi URL dasar.

Tanda tangan Bent lebih mirip dengan

ketik HttpMethods = 'DAPATKAN' | 'PATCH' | ...type StatusCode = angka;ketik BaseUrl = string; // Di sinilah idealnya saya perlu melihat apakah string cocok dengan http(s):type Headers = { [x: string]: any; };
ketik Opsi = HttpMethods | Kode Status | Url Dasar | Header;
fungsi bengkok(...args: Opsi[]): RequestFunctionfungsi bengkok(...args: (Opsi | 'json')[]): RequestFunction// dan seterusnya

Namun memiliki BaseUrl sebagai string menyerap HttpMethods dan kembali
ketik serikat pekerja, yang berakhir hanya sebagai string. Memilikinya hanya sebagai string
juga "tidak tepat" cocok dengan cara kerja bengkok, karena ia memeriksa keberadaannya
dari ^http: atau ^https: untuk menentukan apa yang harus digunakan sebagai basis
url.

Jika kami memiliki tipe regex, saya dapat mendefinisikan BaseUrl sebagai tipe BaseUrl = /^https?:/,
dan ini idealnya akan memverifikasi string yang bukan metode HTTP atau
penyandian respons, serta tidak menyerapnya ke dalam tipe string.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVREXG43VMWS2ZcomEDNOR5KAWZJKTBY5NMVXHJKT
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

Pikiran saya tentang use case adalah untuk mendeteksi tipe parameter ke suatu fungsi.

Pada dasarnya saya memiliki format regex string yang terdefinisi dengan baik yang mewakili pengidentifikasi. Saya bisa menggunakan dekorator, tetapi tipe string yang diperluas memungkinkan saya menggunakan tipe untuk mewakili pengidentifikasi yang diteruskan ke fungsi.

Untuk mengulangi, kami memerlukan contoh kode JavaScript yang ingin Anda tulis dengan cara diketik - jika tidak, kami hanya dapat menebak apa yang Anda coba modelkan (dan apakah sudah ada cara untuk memodelkannya).

@DanielRosenwasser Di bawah ini adalah contoh kode yang ingin kami terapkan pengetikan. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + ju6 + wah ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt sepertinya Anda menginginkan tipe nominal, bukan tipe RegExp? Anda tidak mengharapkan penelepon muncul dengan pemanggilan acak yang divalidasi situs seperti ini:

// 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 fakta bahwa Anda dapat menggambarkan UUID dengan ekspresi reguler adalah artefak dari format string itu sendiri, sedangkan apa yang Anda coba ekspresikan adalah bahwa UUID adalah tipe khusus yang format dukungannya berupa string .

Jadi kombinasi dari 3.7 Assertion Functions dan nominal Fitur dapat melakukan ini (?)

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

  }
}

Apakah ini akan gagal juga?

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

Setelah berpikir sejenak, saya melihat masalah dengan solusi yang saya usulkan
asserts hanya memicu kesalahan saat runtime ->
Regex-Validation dapat memicu kesalahan waktu kompilasi -> 👍
Kalau tidak, proposal ini tidak masuk akal

Sunting:
Masalah lain: someFunc(uuid: any): asserts uuid is UUID tidak mengembalikan UUID, ia melempar atau mengembalikan is UUID -> true .
Jadi saya tidak dapat menggunakan fungsi ini untuk menetapkan UUID dengan cara ini ke mainUser

@RyanCavanaugh Kami ingin ini diketik dengan benar untuk 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)

Kami ingin menolak ini secara statis:

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

Idealnya, kami juga ingin menolak ini secara statis, tetapi ini bukan prioritas tinggi dan kami dapat bertahan tanpa mereka:

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

Ini akan membutuhkan definisi tipe yang jauh lebih rumit, di mana kita memerlukan pesan kegagalan pemeriksaan tipe khusus untuk membantu pengguna mencari tahu mengapa gagal mengetik cek.

Pustaka hiperskrip dan kerangka kerja berbasis hiperskrip lainnya seperti reaksi-hiperskrip juga memiliki masalah yang sama.

Semoga ini membantu!

@isiahmeadows cara yang lebih baik bagi Anda untuk menggunakan beberapa bentuk pembuat string pemilih, yang akan mengembalikan string bermerek, dengan pengetikan yang benar. Suka:

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

@anion155 Ada cara lain untuk mencapainya juga, tetapi ini tentang mengetik perpustakaan yang API-nya dirancang oleh penulis aslinya pada tahun 2014. Jika saya mendesain API-nya sekarang, saya mungkin akan menggunakan m("div", {...attrs}, ...children) dengan tidak ada gula hiperskrip (lebih mudah diketik, lebih sederhana untuk diproses), tetapi sekarang sudah terlambat untuk berbuat banyak.

Saya punya BANYAK untuk mengatakan. Namun, saya tidak sabar. Jadi, saya akan melepaskan pikiran saya sedikit demi sedikit.

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

Mengenai "precisionitis" (man, saya suka kata itu),
Saya pikir kita tidak perlu terlalu mengkhawatirkannya.

Sistem tipe sudah turing lengkap.
Ini pada dasarnya berarti kita bisa sangat tepat tentang banyak hal.
(Seperti, memodelkan semua SQL? Plug tak tahu malu =P)

Tetapi Anda tidak melihat (terlalu banyak) orang yang habis-habisan, dan menggunakan semua jenis operator dengan cara gila yang menghalangi perpustakaan agar tidak kompatibel satu sama lain. Saya suka berpikir bahwa penulis perpustakaan cenderung cukup berkepala dingin... Benar?

Tidak sering saya menginginkan tipe pola string/tipe string yang divalidasi regex tetapi mereka pasti akan membantu meningkatkan keamanan tipe basis kode saya.


Gunakan Kasus

Dari atas kepala saya, saya bisa memikirkan satu contoh baru-baru ini. (Ada banyak lagi tapi aku makhluk yang pelupa)

Saat berintegrasi dengan API Stripe (platform pemrosesan pembayaran), mereka menggunakan ch_ untuk charge -pengidentifikasi terkait, re_ untuk refund -pengidentifikasi terkait, dll.

Akan lebih baik untuk menyandikannya dengan PatternOf</^ch_.+/> dan PatternOf</^re_.+/> .

Dengan cara ini, ketika membuat kesalahan ketik seperti,

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

Saya akan mendapatkan kesalahan,

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

Meskipun saya menyukai tipe nominal/tag, mereka jauh lebih tidak ergonomis dan rawan kesalahan.
Saya selalu melihat tipe nominal/tagged sebagai pilihan terakhir , karena itu berarti ada sesuatu yang tidak dapat dimodelkan oleh sistem tipe TS.

Juga, jenis yang ditandai sangat bagus untuk jenis hantu.
Jenis nominal pada dasarnya tidak pernah berguna.
(Oke, saya mungkin bias. Mereka berguna hanya karena unique symbol Tapi saya suka berpikir saya tidak sepenuhnya salah.)

Pola "ValueObject" untuk validasi bahkan lebih buruk dan saya tidak akan repot membicarakannya.


Perbandingan

Di bawah ini, saya akan membandingkan yang berikut,

  • Tipe pola string/tipe string yang divalidasi regex
  • Jenis nominal
  • Jenis tag struktural

Kita semua bisa setuju bahwa pola "ValueObject" adalah solusi terburuk, dan tidak mempermasalahkannya dalam perbandingan, bukan?


Jenis pola 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

Lihat itu.

  • Sempurna untuk string literal.
  • Tidak terlalu buruk untuk string non-literal.

Jenis nominal...

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

Lihat itu.

  • MENGERIKAN untuk string literal.
  • Setelah mengatasi rintangan literal string, itu tidak terlalu buruk... Benar?

Tetapi kasus penggunaan utama untuk proposal ini adalah literal string.
Jadi, ini adalah alternatif yang mengerikan.


Jenis tag struktural...

Jenis tag struktural tidak jauh berbeda dengan jenis nominal ...

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

Lihat itu.

  • MENGERIKAN untuk string literal.
  • Setelah mengatasi rintangan literal string, itu tidak terlalu buruk... Benar?

Tetapi kasus penggunaan utama untuk proposal ini adalah literal string.
Jadi, ini adalah alternatif yang mengerikan.

Juga, contoh tipe tag struktural ini adalah salinan-tempel literal (ha, pun) dari contoh tipe nominal .

Satu-satunya perbedaan adalah bagaimana tipe StripeChargeId dan StripeRefundId dideklarasikan.

Meskipun kodenya pada dasarnya sama, tipe struktural lebih baik daripada tipe nominal. (Saya akan mengklarifikasi ini di posting berikutnya, saya bersumpah).


Kesimpulan

Ini hanya kesimpulan untuk komentar ini! Bukan kesimpulan dari keseluruhan pemikiran saya!

Tipe pola string/tipe string yang divalidasi regex lebih ergonomis daripada tipe tag nominal/struktural. Mudah-mudahan, contoh sederhana saya tidak terlalu dibuat -


Kesimpulan (Ekstra)

Sebisa mungkin, cara untuk mengambil subset dari tipe primitif harus selalu lebih disukai daripada tipe nominal/tag struktural/nilai-objek.

Contoh pengambilan subset dari tipe primitif,

  • string literal
  • number literal (tidak termasuk NaN, Infinity, -Infinity )
  • boolean literal
  • bigint literal
  • Bahkan unique symbol hanya mengambil sebagian dari symbol

Dari contoh di atas, hanya boolean yang "cukup terbatas". Ini hanya memiliki dua nilai.
Pengembang puas dengan memiliki true dan false literal karena tidak banyak lagi yang bisa diminta.


Jenis number adalah finite-ish tetapi memiliki begitu banyak nilai, kita mungkin juga menganggapnya tak terbatas.
Ada juga lubang di literal apa yang bisa kita tentukan.

Inilah sebabnya mengapa jenis nomor rentang, dan masalah NaN, Infinity, -Infinity sangat populer, dan terus bermunculan. Mampu menentukan sekumpulan nilai kecil yang terbatas, dari himpunan tak terbatas tidak cukup baik.

Menentukan rentang adalah salah satu ide paling umum/alami untuk datang kepada seseorang ketika mereka perlu menentukan subset besar hingga/tak terbatas dari himpunan tak terbatas.


Tipe bigint pada dasarnya tidak terbatas, hanya dibatasi oleh memori.

Ini juga berkontribusi pada popularitas masalah jenis nomor rentang.


Tipe string pada dasarnya tidak terbatas, hanya dibatasi oleh memori.

Dan inilah mengapa masalah tipe string yang divalidasi tipe pola string/regex ini sangat populer.

Menentukan regex adalah salah satu ide paling umum/alami yang datang kepada seseorang ketika mereka perlu menentukan subset besar hingga/tak terbatas dari himpunan tak terbatas.


Jenis symbol ... Ini juga tak terbatas. Dan juga tidak terbatas, cukup banyak.

Tetapi elemen dari tipe symbol semuanya hampir tidak berhubungan satu sama lain, hampir dalam segala hal. Dan, jadi, tidak ada yang membuat masalah untuk bertanya, "Dapatkah saya memiliki cara untuk menentukan subset besar yang terbatas/tak terbatas dari symbol ?".

Bagi kebanyakan orang, pertanyaan itu bahkan tidak masuk akal. Tidak ada cara yang berarti untuk melakukan ini (kan?)


Namun, hanya dapat mendeklarasikan himpunan bagian dari primitif tidak terlalu berguna. Kami juga butuh,

  • Literal dengan tipe yang tepat harus dapat dialihkan tanpa pekerjaan lebih lanjut

Untungnya, TS cukup waras untuk mengizinkan ini.

Bayangkan tidak dapat meneruskan false ke (arg : false) => void !

  • Cara penyempitan bawaan

    Saat ini, untuk literal ini, kami memiliki == & === sebagai cara penyempitan bawaan.

    Bayangkan perlu menulis penjaga tipe baru untuk setiap literal!

Masalah dengan jenis nominal/tag struktural/nilai-objek adalah bahwa mereka pada dasarnya gagal memenuhi dua kriteria di atas. Mereka mengubah tipe primitif menjadi tipe kikuk yang bukan tipe objek, tetapi harus ditangani seperti tipe objek.

Ergonomi

Oke, berikut penjabaran lebih lanjut tentang pola string vs tipe tag nominal vs struktural.

Argumen ini juga berlaku untuk https://github.com/microsoft/TypeScript/issues/15480 .


Kompatibilitas Lintas Perpustakaan

Jenis nominal adalah yang terburuk dalam kompatibilitas lintas-perpustakaan.
Ini seperti menggunakan unique symbol di dua perpustakaan dan mencoba membuatnya saling beroperasi.
Itu tidak bisa dilakukan.
Anda perlu menggunakan pelindung tipe boilerplate, atau trust-me-operator ( as ).

Anda juga akan membutuhkan lebih banyak boilerplate untuk penegasan.

Jika tipenya tidak memerlukan kompatibilitas lintas-perpustakaan, maka menggunakan tipe nominal tidak masalah...
Bahkan jika sangat tidak ergonomis (lihat contoh di atas).


Untuk tipe struktural, jika perpustakaan A memiliki,

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

Dan perpustakaan B memiliki,

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

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

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

Maka Anda memerlukan pelindung tipe boilerplate, atau trust-me-operator ( as ).

Anda juga akan membutuhkan lebih banyak boilerplate untuk penegasan.

Jika tipenya tidak memerlukan kompatibilitas lintas-perpustakaan, maka menggunakan tipe struktural baik-baik saja ...
Bahkan jika sangat tidak ergonomis (lihat contoh di atas).


Untuk tipe pola string, jika perpustakaan A memiliki,

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

Dan perpustakaan B memiliki,

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

Asumsikan kedua perpustakaan selalu menghasilkan string untuk StripeChargeId yang akan memenuhi persyaratan kedua perpustakaan. Perpustakaan A hanya "malas" dengan validasinya. Dan perpustakaan B "lebih ketat" dengan validasinya.

Kemudian, itu agak mengganggu. Tapi tidak terlalu buruk.
Karena Anda bisa menggunakan libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) sebagai typeguard. Tidak perlu menggunakan trust-me-operator ( as ).

Anda masih membutuhkan boilerplate untuk pelindung pernyataan.

Jika tipe tidak memerlukan kompatibilitas lintas perpustakaan, maka menggunakan tipe pola string sangat cocok, dan juga sangat ergonomis.


Jika Anda membutuhkan kompatibilitas lintas pustaka, tipe pola string masih lebih baik daripada tipe tag struktural! Dengarkan aku.

Jika domain yang dimodelkan dipahami dengan baik, maka sangat mungkin bahwa banyak penulis perpustakaan yang terisolasi akan berakhir dengan menulis regex yang sama. Dengan tipe tag struktural, mereka semua bisa menulis properti dan tipe apa pun yang mereka inginkan di dalam tag.

Jika ada format string yang menentukan standar untuk apa pun yang sedang dimodelkan, maka pada dasarnya dijamin bahwa semua penulis perpustakaan akan menulis regex yang sama! Jika mereka menulis regex yang berbeda, mereka tidak benar-benar mengikuti standar. Apakah Anda ingin menggunakan perpustakaan mereka? Dengan jenis tag struktural, mereka semua masih bisa menulis apa saja. (Kecuali seseorang memulai standar jenis tag struktural yang akan dipedulikan semua orang? Lol)


Kompatibilitas Lintas-Versi

Seperti biasa, tipe nominal adalah yang terburuk dalam kompatibilitas lintas versi.
Oh, Anda menabrak tambalan perpustakaan Anda, atau versi kecil?
Jenis decalarationnya masih sama?
Kodenya masih sama?
Tidak. Mereka tipe yang berbeda.

image


Jenis tag struktural masih dapat ditetapkan, di seluruh versi (bahkan versi utama), selama jenis tag secara struktural sama.


Jenis pola string masih dapat ditetapkan, di seluruh versi (bahkan versi utama), selama regexnya sama.

Atau kita bisa menjalankan algoritma lengkap PSPACE untuk menentukan apakah regexnya sama? Kami juga dapat menentukan subkelas regex mana yang paling umum dan menjalankan algoritme kesetaraan yang dioptimalkan untuk itu... Tapi itu terdengar seperti banyak usaha.

Pemeriksaan subtipe regex akan keren untuk dimiliki, dan pasti akan membuat penggunaan tipe pola string lebih ergonomis. Sama seperti bagaimana pemeriksaan subtipe rentang akan menguntungkan proposal tipe rentang nomor.

[Sunting]
Dalam komentar ini,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Seseorang yang terhubung dengan,
https://bora.uib.no/handle/1956/3956

Berjudul, "Masalah Penyertaan untuk Ekspresi Reguler"
[/Sunting]


pelat ketel

TODO (Tetapi kita dapat melihat bahwa tipe pola string memiliki jumlah boilerplate paling sedikit)

Doa harfiah

TODO (Tetapi kita dapat melihat bahwa tipe pola-string mendukung pemanggilan literal yang terbaik)

Doa Non-Harfiah

TODO (Tetapi kita dapat melihat bahwa tipe pola-string mendukung pemanggilan non-literal yang terbaik)

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

TypeScript tidak dapat membuat kesalahan pada instantiasi persimpangan, jadi ini tidak akan menjadi bagian dari desain akhir apa pun.

Saya tidak tahu mengapa orang ingin melarang persimpangan, tetapi Anda benar sekali bahwa melarang itu tidak masuk akal.


sehingga melanggar setiap doa nonliteral?

Yah, tidak setiap doa non-harfiah.

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
}

Melanggar doa non-literal, di mana itu tidak dapat membuktikan bahwa itu cocok dengan regex, tidak selalu merupakan hal yang buruk. Ini hanya masalah keamanan tipe.

Itu seperti mengatakan bahwa string literal buruk karena sekarang pemanggilan non-literal gagal.
Tipe pola string/tipe string yang divalidasi regex memungkinkan Anda menentukan gabungan dari literal string dalam jumlah tak terbatas.


penggunaan nonliteral apa pun akan memerlukan pengujian ulang atau pernyataan:

Saya tidak melihat itu sebagai masalah sama sekali.
Sama halnya dengan tipe nominal/tagged sekarang.
Atau mencoba meneruskan string ke fungsi yang mengharapkan literal string.
Atau mencoba meneruskan tipe yang lebih luas ke tipe yang lebih sempit.

Dalam kasus khusus ini, Anda telah menunjukkan bahwa const ZipCode = /^\d\d\d\d\d$/; dan ZipCode.test(s) dapat bertindak sebagai pelindung tipe. Ini tentu akan membantu dengan ergonomi.


  • Masalah yang sedang dipecahkan tidak memiliki alternatif yang lebih baik (termasuk alternatif yang masuk akal yang belum ada dalam bahasa tersebut)

Yah, semoga saya telah menunjukkan bahwa jenis tag nominal/struktural bukanlah alternatif yang lebih baik. Mereka sebenarnya sangat buruk.

  • Masalahnya terjadi dengan frekuensi yang berarti dalam basis kode nyata

Uhh ... Biarkan saya kembali kepada Anda tentang yang satu itu ...

  • Solusi yang diusulkan memecahkan masalah itu dengan baik

Jenis pola string yang diusulkan tampaknya cukup bagus.


TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

Pandangan Anda adalah bahwa tipe nominal/tag cukup baik untuk penggunaan non-literal.
Jadi, kasus penggunaan apa pun yang muncul yang menunjukkan penggunaan non-literal tidak cukup baik, karena tipe nominal/tagged menutupinya.

Namun, kita telah melihat bahwa, bahkan untuk penggunaan non-harfiah,

  • Jenis tag nominal/struktural mengalami masalah kompatibilitas lintas-perpustakaan/versi
  • Jumlah boilerplate untuk jenis tag nominal/struktural secara signifikan lebih banyak daripada boilerplate untuk jenis pola string

Juga, tampaknya kasus penggunaan literal yang diajukan tidak memuaskan bagi Anda, karena mereka mencoba dan melakukan hal-hal konyol seperti validasi email, atau menggunakan regex yang tidak cukup akurat.


Menulis tes - di sinilah input hardcoded masuk akal, meskipun ini hampir merupakan tandingan karena kode pengujian Anda mungkin harus memberikan banyak input yang tidak valid

Kasus penggunaan yang baik yang diangkat adalah menulis tes run-time . Dan Anda benar, bahwa mereka juga harus membuang banyak input yang tidak valid untuk pengujian run-time.

Tapi itu bukan alasan untuk tidak mendukung tipe pola string. Mungkin saja mereka ingin menguji input yang valid dalam file tertentu dan secara tidak sengaja memberikan input yang tidak valid.

Tetapi, karena mereka harus menggunakan tipe guard atau trust-me-operator ( as ) atau objek nilai, sekarang mereka akan mendapatkan kesalahan run-time, alih-alih mengetahui bahwa tes akan gagal sebelumnya .

Menggunakan trust-me-operator ( as ) untuk pengujian run-time hanya boleh dilakukan untuk pengujian input yang tidak valid. Saat ingin menguji input yang valid, lebih jelas untuk tidak memerlukan peretasan untuk menetapkan literal ke jenis tag nominal/struktural.

Jika mereka pernah mengubah regex di masa mendatang, alangkah baiknya jika pengujian mereka sekarang gagal berjalan, karena masalah penugasan. Jika mereka hanya menggunakan as di mana-mana dalam pengujian mereka, mereka tidak akan tahu sampai mereka menjalankan pengujian.

Dan jika penulis perpustakaan hanya menggunakan as mana-mana saat melakukan dogfood perpustakaan mereka sendiri... Bagaimana dengan konsumen hilir? Tidakkah mereka juga tergoda untuk menggunakan as mana-mana dan mengalami masalah run-time saat meningkatkan ke versi baru?

Dengan tipe pola-string, ada lebih sedikit alasan untuk menggunakan as mana saja dan baik penulis perpustakaan maupun konsumen hilir akan mengetahui perubahan perubahan dengan lebih mudah.

(Agak bertele-tele tapi saya harap beberapa poin saya berhasil).


Juga, saya menulis banyak tes waktu kompilasi (Dan saya tahu tim TS juga melakukannya).

Akan lebih baik jika saya dapat menguji bahwa literal string akan gagal/lulus pemeriksaan regex dalam tes waktu kompilasi saya. Saat ini, saya tidak dapat memiliki tes waktu kompilasi untuk hal-hal ini dan sebagai gantinya perlu menggunakan tes run-time.

Dan jika gagal/lulus tes waktu kompilasi saya, maka saya akan yakin bahwa konsumen hilir dapat menggunakan literal string tersebut (atau yang serupa) dan mengharapkan mereka berperilaku dengan cara yang benar.


Sepertinya ini dengan cepat menempatkan kita di jalan menuju situasi Menara Babel...

Ini bahkan lebih benar menggunakan jenis tag nominal/struktural, sebenarnya. Seperti yang ditunjukkan oleh contoh di atas, mereka sangat cocok untuk kompatibilitas lintas-perpustakaan/versi ...

Namun, tipe regex/string-pattern memiliki peluang yang layak untuk tidak jatuh ke dalam masalah itu (semoga, berkat standardisasi, dan penulis perpustakaan yang waras).


EDIT

Hal yang umum di utas adalah bahwa regex ini akan membantu memvalidasi kode pengujian, karena meskipun dalam skenario produksi kode akan berjalan melawan string yang disediakan runtime daripada literal hardcoded, Anda masih menginginkan beberapa validasi bahwa string pengujian Anda adalah " benar". Namun, ini tampaknya menjadi argumen untuk string nominal/tag/bermerek, karena Anda akan menulis fungsi validasi dengan cara apa pun, dan manfaat tes adalah Anda tahu mereka berjalan secara mendalam (sehingga kesalahan apa pun dalam input pengujian akan ditandai di awal siklus pengembangan).

Ah... Seharusnya aku membaca semuanya dulu sebelum menulis ini...

Bagaimanapun, saya punya beberapa contoh, di mana tipe pola string berguna.


Perpustakaan Deklarasi Rute HTTP

Dengan perpustakaan ini, Anda dapat membuat objek deklarasi rute HTTP. Deklarasi ini digunakan oleh klien dan server.

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

Ini adalah batasan untuk .append() ,

  • String literal saja (Tidak dapat menerapkan ini saat ini, tetapi jika Anda menggunakan non-literal, pembuat deklarasi rute menjadi sampah)
  • Harus dimulai dengan garis miring ke depan ( / )
  • Tidak boleh diakhiri dengan garis miring ke depan ( / )
  • Tidak boleh mengandung karakter conlon ( : ); itu dicadangkan untuk parameter
  • Tidak boleh mengandung dua, atau lebih, garis miring ke depan secara berurutan ( // )

Saat ini, saya hanya memiliki pemeriksaan run-time untuk ini, yang menimbulkan kesalahan. Saya ingin konsumen hilir harus mengikuti batasan ini tanpa perlu membaca beberapa komentar Github README atau JSDoc. Tulis saja jalurnya dan lihat garis berlekuk merah.


Hal-hal lain

Saya juga memiliki regex untuk string heksadesimal, string alfanumerik.

Saya juga punya ini,

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

saya melihat ini,

Integer - salah menolak "3e5"

Saya juga punya ini, yang bukan integer regex tetapi menggunakan 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,
    };
}

Saya juga punya komentar ini, meskipun,

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

Konstruktor RegExp

Lucunya, konstruktor RegExp akan mendapat manfaat dari tipe string yang divalidasi regex!

Saat ini, itu,

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

Namun, kita bisa memiliki,

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

TL;DR (Tolong baca, saya berusaha keras untuk ini :cry: )

  • Jenis pola string lebih ergonomis daripada jenis tag nominal/struktural

    • Lebih sedikit boilerplate

  • Tipe pola string lebih kecil kemungkinannya dibandingkan tipe tag nominal/struktural untuk menjadi situasi Menara Babel

    • Terutama dengan pemeriksaan subtipe regex

  • Tipe pola-string adalah cara paling alami untuk mendefinisikan himpunan bagian hingga/tak terhingga besar dari tipe string

    • Memperkenalkan fitur ini bahkan mungkin membuat orang berpikir tentang format string yang valid untuk perpustakaan mereka lebih dekat!

  • Jenis pola string memungkinkan keamanan waktu kompilasi yang lebih kuat untuk beberapa perpustakaan (Biarkan saya kembali kepada Anda tentang prevalensi... run away )

    • Konstruktor RegExp, string hex/alfanumerik, deklarasi jalur rute, pengidentifikasi string untuk database, dll.


Mengapa regex Anda begitu buruk?

Sekelompok kasus penggunaan yang diajukan oleh orang lain ingin memperkenalkan tipe pola-string agar sesuai dengan perpustakaan yang

Sering kali, saya merasa perpustakaan yang ada ini bahkan tidak menggunakan regex sebanyak itu untuk memvalidasi input mereka. Atau, mereka menggunakan regex untuk melakukan validasi sederhana. Kemudian, mereka menggunakan parser yang lebih rumit untuk melakukan validasi yang sebenarnya.

Tapi ini adalah kasus penggunaan yang valid untuk tipe pola string!


Jenis pola string untuk memvalidasi superset dari nilai string yang valid

Tentu, string yang dimulai dengan / , tidak diakhiri dengan / , tidak mengandung / berturut-turut, dan tidak mengandung : akan melewati " Regex jalur HTTP". Tapi ini hanya berarti bahwa kumpulan nilai yang melewati regex ini adalah superset dari jalur HTTP yang valid.

Lebih jauh ke bawah, kami memiliki pengurai jalur URL aktual yang memeriksa bahwa ? tidak digunakan, # tidak digunakan, beberapa karakter diloloskan, dll.

Tetapi dengan tipe pola string sederhana ini, kami telah menghilangkan kelas besar dari masalah umum yang mungkin dihadapi pengguna perpustakaan! Dan kami juga menghilangkannya selama waktu kompilasi!

Tidak sering pengguna menggunakan ? di jalur HTTP mereka, karena sebagian besar cukup berpengalaman untuk mengetahui bahwa ? adalah awal dari string kueri.


Saya baru menyadari bahwa Anda sudah mengetahui kasus penggunaan ini.

Utas ini menyiratkan berbagai macam kasus penggunaan; contoh konkret lebih jarang. Masalahnya, banyak dari contoh ini tampaknya tidak lengkap - mereka menggunakan RegExp yang akan menolak input yang valid.

Jadi, tentu saja, banyak regex yang diusulkan tidak "lengkap".
Tapi selama mereka tidak menolak input yang valid, seharusnya tidak apa-apa, kan?

Tidak apa-apa jika mereka mengizinkan input yang tidak valid, bukan?
Karena kita bisa memiliki parser "nyata" selama run-time menangani validasi penuh.
Dan pemeriksaan waktu kompilasi dapat menghilangkan banyak masalah umum bagi pengguna hilir, meningkatkan produktivitas.

Contoh-contoh yang menolak input yang valid harus cukup mudah untuk dimodifikasi, sehingga mereka tidak menolak input yang valid, tetapi mengizinkan input yang tidak valid.


Jenis dan persimpangan pola string

Bagaimanapun, tipe persimpangan pada tipe pola string akan sangat berguna!

Contoh .append() dapat ditulis sebagai,

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</\/\//> juga bisa,
PatternOf</^((([/])(?!\3))|[^/])+$/> tapi ini jauh lebih rumit

Terima kasih, @AnyhowStep , atas demonstrasi ekstensifnya. Saya ingin mengkritik Anda karena membuat saya banyak membaca, tetapi ternyata sangat membantu!

Saya sering kesulitan mengetik apis internal saya yang penuh dengan parameter string, dan saya pasti akan berakhir dengan banyak persyaratan yang muncul saat run-time. Mau tidak mau, konsumen saya perlu menduplikasi pemeriksaan pola ini, karena mereka tidak menginginkan pengecualian, mereka menginginkan cara khusus untuk menangani kegagalan.

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

Dalam dunia string dan pola, string generik hampir sama dengan unknown , menghapus banyak keamanan tipe demi pemeriksaan runtime, dan menyebabkan ketidaknyamanan bagi pengembang saya yang mengonsumsi.

Untuk beberapa kasus penggunaan yang disebutkan, hanya sebagian kecil Regex yang diperlukan, misalnya pencocokan awalan.

Secara potensial ini dapat dilakukan dengan fitur bahasa TS yang lebih umum seperti Variadic Kinds #5453 dan tipe inferensi saat menyebarkan tipe literal string.

Spekulasi masa depan:

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;

Untuk beberapa kasus penggunaan yang disebutkan, hanya sebagian kecil Regex yang diperlukan, misalnya pencocokan awalan.

Saya masih mendukung proposal saya , yang menawarkan ini + beberapa hal lain, pada dasarnya superset bahasa bebas bintang yang sangat kecil, Anda masih dapat memeriksa subsetting dan kesetaraan secara efisien. Dan sejauh ini, saya belum melihat proposal lain yang berupaya untuk mengatasi aspek kinerja ekspresi reguler yang sewenang-wenang, perhatian terbesar yang dimiliki tim TS.

Masalah dengan bahasa bebas bintang adalah, seperti namanya, Anda tidak dapat menggunakan bintang, yang membuatnya sulit untuk memvalidasi hal-hal seperti url. Selain itu, kebanyakan orang mungkin akan menginginkan bintang, dan hanya menggunakan jumlah sewenang-wenang dari urutan berulang untuk meniru mereka, yang akan membuat sulit untuk memeriksa himpunan bagian.

Dan kinerja sebagian besar regex yang dapat diwakili DFA normal tidak terlalu buruk, dan dimungkinkan untuk memeriksa ini untuk sub/superset.

Anda masih bisa mendapatkan * .

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

@TijmenW Baca proposal saya sedikit lebih dekat - ada beberapa alasan tersembunyi di sana, dan beberapa fitur kecil yang membuatnya benar-benar praktis. Ini tidak secara langsung terbatas pada menentukan tata bahasa bebas bintang, tetapi superset kecil diperluas dengan cukup untuk membuatnya praktis berguna untuk kasus penggunaan semi-lanjutan saya. Secara khusus, Anda dapat melakukan starof ('a' | 'b' | ...) untuk karakter individu dan Anda dapat menggunakan string sebagai setara dengan starof UnionOfAllCodePoints (sehingga membuatnya tidak lagi primitif dalam teori).

Juga, memeriksa apakah bahasa reguler cocok dengan subset dari apa yang cocok dengan bahasa reguler lainnya adalah NP-complete dan setara dengan masalah isomorfisme subgraf umum. Inilah sebabnya mengapa Anda tidak bisa hanya melakukan bahasa reguler standar, dan mengapa saya mencoba membatasi starof sebanyak mungkin, untuk mencoba menjaga kompleksitas komputasi teoritis tetap rendah.

TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

Ambil ini dengan sebutir garam, karena ini adalah perpustakaan baru, tetapi perpustakaan apa pun seperti https://github.com/ostrowr/ts-json-validator akan dibuat jauh lebih berguna dengan sesuatu seperti tipe regex.

Tujuan perpustakaan adalah untuk menghasilkan pasangan skema TypeScript/JSON <T, s> sedemikian rupa sehingga

  1. Jenis apa pun yang dapat divalidasi oleh s dapat ditetapkan ke T
  2. Sesedikit mungkin jenis yang dapat ditetapkan ke T validasi gagal saat dijalankan melawan s .

Jenis regex akan meningkatkan keketatan (2) dengan mengizinkan jenis yang divalidasi menjadi lebih ketat tentang setidaknya kata kunci berikut:

  • format
  • patternProperties
  • propertyNames

TODO: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan.

Semua pustaka antarmuka Excel dapat menggunakan validasi tipe sebagai A1 atau A5:B7 .

Kunci Properti / Pengindeks String Regex

Beberapa perpustakaan memperlakukan objek sesuai dengan nama properti. Misalnya, di React kami ingin menerapkan tipe ke prop apa pun yang namanya dimulai dengan aria- :

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

Ini secara efektif merupakan konsep ortogonal (kita dapat menambahkan tipe Regex tanpa menambahkan kunci properti Regex, dan sebaliknya).

Saya tahu ini agak ortogonal untuk semua yang terjadi di sini, tetapi Wesley berpikir Anda bisa menggunakan masukan kami. Ini terus muncul di Fabric karena berbagai alasan. Sebagai pustaka komponen, kami ingin dapat meningkatkan antarmuka props komponen yang secara akurat mencerminkan antarmuka komponen React yang diizinkan oleh TypeScript, termasuk atribut data- dan aria- . Tanpanya, kami tidak dapat meningkatkan antarmuka yang akurat kepada konsumen kami untuk digunakan untuk atribut ini. Ini menjadi masalah yang lebih besar dengan versi Fabric berikutnya di mana kita melihat implementasi yang dapat dicolokkan seperti slot dan perlu mendefinisikan dan mengizinkan atribut ini pada antarmuka yang berubah-ubah.

Jika ada yang bisa kami bantu, beri tahu saya! 😄

Taman Bermain 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: Tolong bantu kami dengan mengidentifikasi fungsi perpustakaan nyata yang dapat mengambil manfaat dari jenis RegExp, dan ekspresi aktual yang akan Anda gunakan

Pekerjaan Cron. (sangat terkejut ini tidak disebutkan)

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

Hanya membuang dua sen saya di sini - Saya sedang mengerjakan proyek React di mana kami ingin memvalidasi prop yang akan digunakan sebagai atribut HTML id . Ini berarti harus memenuhi aturan berikut atau perilaku tak terduga akan terjadi:

  1. Memiliki setidaknya satu karakter
  2. Tidak memiliki spasi

Dengan kata lain:

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

Contoh lain: sanctuary-type-identifier dengan string yang diharapkan dalam format '<namespace>/<name>[@<version>]'

Kasus penggunaan: API DOM yang diketik dengan string seperti Navigator.registerProtocolHandler() .

Mengutip MDN:

Untuk alasan keamanan, registerProtocolHandler() membatasi skema mana yang dapat didaftarkan.

Skema kustom dapat didaftarkan selama:

  • Nama skema kustom dimulai dengan web+
  • Nama skema kustom mencakup setidaknya 1 huruf setelah awalan web+
  • Skema kustom hanya memiliki huruf kecil ASCII dalam namanya.

Dengan kata lain, Navigator.registerProtocolHandler() mengharapkan string atau string tetapi hanya jika sesuai dengan skema tertentu.

Properti Kustom CSS untuk CSSType adalah kasus penggunaan lain untuk menyediakan tipe tertutup untuk semua properti kecuali yang diawali dengan -- .

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

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

Dapatkah seseorang memberi tahu saya apakah ini sama dengan jenis penyempurnaan? https://github.com/microsoft/TypeScript/issues/7599

@ gautam1168 Ini secara teoritis hanya subset, di mana itu menyempurnakan tipe string secara khusus. (Jenis numerik memiliki masalah mereka sendiri, tentu saja.)

Untuk beberapa kasus penggunaan yang disebutkan, hanya sebagian kecil Regex yang diperlukan, misalnya pencocokan awalan.

Saya masih mendukung proposal saya , yang menawarkan ini + beberapa hal lain, pada dasarnya superset bahasa bebas bintang yang sangat kecil, Anda masih dapat memeriksa subsetting dan kesetaraan secara efisien. Dan sejauh ini, saya belum melihat proposal lain yang berupaya untuk mengatasi aspek kinerja ekspresi reguler yang sewenang-wenang, perhatian terbesar yang dimiliki tim TS.

Dalam komentar ini,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Seseorang yang terhubung dengan,
https://bora.uib.no/handle/1956/3956

Berjudul, "Masalah Penyertaan untuk Ekspresi Reguler"


Namun,

  • Jika ekspresi tangan kanan adalah 1-tidak ambigu, algoritme memberikan jawaban yang benar.
  • Jika tidak, mungkin memberikan jawaban yang benar, atau tidak ada jawaban.

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

(Tentu saja, ekspresi reguler JS tidak teratur)

@AnyhowStep Itu mungkin berhasil - Saya baru saja mencabut batasan starof dan mengubah batasan itu. Saya ingin cara yang lebih baik mencirikan pembatasan, karena matematika agak abstrak dan tidak jelas bagaimana konkret akan berlaku dalam praktek (tidak semua orang yang akan menggunakan jenis yaitu berpengalaman dalam bahasa formal).

Juga, secara terpisah, saya sangat menginginkan alternatif yang lebih baik untuk starof sebagai operator untuk memodelkan hal semacam itu.

Saya ingin tahu: Apakah mungkin untuk memutuskan penyertaan/penahanan ekspresi reguler? Menurut wikipedia , itu dapat diputuskan. Namun, apakah ini juga menjelaskan ekspresi reguler di JS? Saya pikir mereka memiliki lebih banyak fitur daripada RE standar (misalnya referensi balik). Jika dapat ditentukan, apakah itu layak secara komputasi?
Ini akan memengaruhi fitur ini (penyempitan):

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

@nikeee Decidable tidak cukup untuk ini menjadi realistis. Bahkan waktu kuadrat umumnya terlalu lambat pada skala ini. Bukan TS, tapi saya punya beberapa latar belakang tentang masalah serupa.

Dalam menghadapi referensi balik, saya menduga itu masih dapat diputuskan, tetapi kemungkinan eksponensial jika tidak lebih buruk. Hanya tebakan yang berpendidikan.

Terima kasih telah mengklarifikasi ini!

Bahkan waktu kuadrat umumnya terlalu lambat pada skala ini.

Inilah sebabnya saya juga bertanya apakah itu layak secara komputasi, jadi saya kira tidak.

Jika hal yang sama berlaku untuk kesetaraan, bukankah itu berarti bahwa hampir semua properti dari fitur ini tidak layak? Koreksi saya, jika saya salah, tetapi sepertinya yang tersisa hanyalah keanggotaan. Saya tidak berpikir bahwa ini saja akan berguna.

@nikeee Perlu diingat bahwa pola ini akan diperiksa secara harfiah setiap properti dari setiap jenis yang dibandingkan dengan . Dan untuk jenis dengan sifat regexp, Anda harus menghitung apakah regexp cocok subset dari apa yang lain pertandingan regexp, binatang yang agak rumit dalam dirinya sendiri.

Bukan tidak mungkin, hanya sulit , dan Anda harus membatasi jika Anda ingin itu layak. (Untuk satu, JS regexps tidak akan berfungsi - mereka tidak hanya tidak cukup dapat diperluas, tetapi juga terlalu fleksibel.)

Sunting: Saya ingin mengulangi ini: Saya tidak berada di tim TS, hanya untuk memperjelas. Saya hanya memiliki latar belakang yang layak dalam desain algoritma CS.

Hmm, jadi mungkin Anda hanya dapat mendukung subset "terbatas" dari RegEx "biasa". Mengingat kasus penggunaan, regex sejauh ini cukup sederhana… (warna, nomor telepon, dll.)

Bagaimana kita bisa mendesain UX yang hanya mendukung sebagian? Mungkin tidak terlalu jelas bagi pengguna bahwa Fitur X dari RegEx berfungsi, tetapi Y tidak.

baik ... jangan menyebutnya "regex" – sebagai permulaan. Mungkin hanya "pencocokan pola" atau lebih :see_no_ evil:. Tapi ya, sepertinya bukan tugas yang mudah…

Bagaimana dengan sintaks non-regex seperti ini:

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'

Dalam pikiran saya, ini akan sangat cocok dengan sistem tipe. Anda dapat menambahkan sintaks yang berbeda untuk hal-hal seperti kecocokan opsional:

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

Tambahkan dukungan untuk quantifiers, operator serakah dan Anda memiliki sesuatu yang cukup kuat. Saya pikir itu mungkin cukup untuk sebagian besar kasus penggunaan yang mungkin ingin digunakan oleh pengembang.

Saya pikir pendekatan ini akan lebih ramah pengguna. Namun, tampaknya setara dengan operasi aritmatika pada tipe.
Menurut https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 dan https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109 , ini adalah keputusan desain untuk tidak melakukannya aritmatika pada jenis.
Jika saya tidak salah, pendekatan ini dapat dengan mudah membuat tipe yang sangat besar. Mempertimbangkan:

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

(ini mengasumsikan implementasi akan menggunakan tipe serikat pekerja. Ini dapat bekerja dengan implementasi yang berbeda/lebih kompleks)

Penafian: Saya bukan bagian dari tim TS dan saya tidak bekerja di TS. Hanya 2c saya.

@rozzzly @nikeee Kurang lebih itulah inti dari proposal saya , hanya dengan beberapa fitur kecil yang hilang. Saya mendasarkan milik saya pada sebagian besar bahasa reguler (konsep bahasa formal), bukan ekspresi reguler dalam arti literal regexp dan semacamnya, jadi itu jauh lebih kuat daripada itu tetapi cukup kuat untuk menyelesaikan pekerjaan.

Saya pikir pendekatan ini akan lebih ramah pengguna. Namun, tampaknya setara dengan operasi aritmatika pada tipe.

Math mengatakan bahwa memvalidasi apakah suatu tipe adalah subtipe dari yang lain secara komputasi setara dengan memeriksa apakah string terkandung dalam bahasa formal yang diberikan.

Validasi domain secara khusus sebenarnya adalah hal yang cukup rumit untuk dilakukan jika Anda juga memeriksa validitas akhiran TLD/ /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + paling banyak 255 karakter, tetapi bahkan ini sangat rumit untuk diketik kecuali Anda menggunakan tata bahasa reguler penuh seperti yang ditunjukkan oleh regexp di atas. Anda dapat secara terprogram menghasilkan tipe dengan cukup mudah (saya akan membiarkannya sebagai latihan untuk pembaca) hanya menggunakan string dari @rozzzly 's atau proposal saya, tetapi hasil akhirnya masih agak rumit.

@isiahmeadows

Itu kurang lebih inti dari proposal saya , hanya dengan beberapa fitur yang lebih kecil hilang.

Terakhir kali saya membaca seluruh utas ini lebih dari setahun yang lalu. Saya sedang istirahat dan melihat pemberitahuan, membaca komentar @rugk tentang _"yah ...jangan menyebutnya "regex" – sebagai permulaan"_ yang membuat saya berpikir ... Saya tidak menyadari seseorang telah melakukannya mengajukan proposal yang jauh lebih rinci untuk ide _(/a very mirip)_ yang pada dasarnya sama.

...bahkan ini sangat rumit untuk diketik kecuali Anda menggunakan tata bahasa reguler penuh seperti yang ditunjukkan oleh regexp di atas. Anda dapat secara terprogram menghasilkan tipe dengan cukup mudah (saya akan membiarkannya sebagai latihan untuk pembaca) hanya menggunakan string dari @rozzzly 's atau proposal saya, tetapi hasil akhirnya masih agak rumit.

Dalam pikiran saya, beberapa fasilitas untuk pencocokan pola terbatas seperti yang saya sarankan izinkan akan sangat berguna untuk pengetikan yang sangat sederhana, dan _yang tidak terlalu ketat_. Contoh yang saya berikan jauh dari tepat, dan tidak akan meledakkan kompiler.

Tetapi seperti yang ditunjukkan oleh @nikeee dan Anda berdua, ini bisa dianggap ekstrem berbahaya. Dengan asumsi implementasi yang paling naif hanya mendukung serikat pekerja. Seseorang akan merusak hari semua orang dengan memublikasikan pembaruan ke @types/some-popular-project yang berisi:

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

Menempatkan itu ke dalam perspektif, persatuan itu terdiri dari jenis yang berbeda yang lebih dari atom di alam semesta diamati .

Sekarang, saya telah melihat beberapa kesalahan penetapan yang sangat panjang tetapi bayangkan kesalahan (tidak terpotong) untuk itu ....

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

Jadi ya .. ada beberapa masalah di sana

@rozzzly Apa yang membuat tipe itu berbeda (dalam hal kelayakan) dari TupleWithLengthBeteen1And64<Charset> ?
Kompiler tidak dipaksa untuk memperluas setiap jenis ke bentuk yang dinormalisasi, itu akan dengan cepat meledak pada jenis yang cukup normal jika itu terjadi.
Tidak mengatakan saya pikir masalah ini masuk akal dalam TypeScript saat ini, jika bahkan "bilangan bulat antara 3 dan 1024" (pikirkan panjang alokasi buffer pesan) dianggap di luar cakupan.

@simonbuchan Setidaknya jenis awalan dan akhiran harus ada, jika tidak ada yang lain. Itu sendiri diperlukan untuk banyak pustaka dan kerangka kerja DOM.

Saya tahu ini telah dipukuli sampai mati dan beberapa proposal bagus telah diberikan. Tetapi saya hanya ingin menambahkan hal-hal tambahan yang mungkin menurut sebagian orang agak menarik.

Dalam menghadapi referensi balik, saya menduga itu masih dapat diputuskan, tetapi kemungkinan eksponensial jika tidak lebih buruk. Hanya tebakan yang berpendidikan.

Referensi balik dapat membuat regexp menggambarkan tata bahasa yang peka konteks, superset tata bahasa bebas konteks. Dan kesetaraan bahasa untuk CFG tidak dapat diputuskan. Jadi lebih buruk lagi untuk CSG, yang setara dengan robot berbatas linier.


Dengan asumsi hanya semua ekspresi reguler yang dapat dikonversi ke DFA digunakan dalam regexp (concat, union, star, persimpangan, komplemen, dll.), Mengonversi regexp ke NFA adalah O(n), mendapatkan produk dari dua NFAs adalah O(m*n), kemudian melintasi grafik yang dihasilkan untuk menerima negara adalah O(m*n). Jadi, memeriksa kesetaraan bahasa/subset dari dua regexp reguler juga O(m*n).

Masalahnya adalah alfabetnya sangat besar di sini. Buku teks biasanya membatasi diri pada abjad ukuran 1-5, ketika berbicara tentang DFA/NFA/ekspresi reguler. Tetapi dengan JS regexps, kami memiliki semua unicode sebagai alfabet kami. Memang, mungkin ada cara yang efisien untuk merepresentasikan fungsi transisi menggunakan sparse arrays dan peretasan dan pengoptimalan cerdas lainnya untuk pengujian kesetaraan/subset...

Saya yakin mungkin untuk melakukan pengecekan tipe untuk penugasan reguler-ke-reguler dengan agak efisien.

Kemudian, semua tugas non-reguler hanya memerlukan pernyataan tipe eksplisit.

Saya baru-baru ini mengerjakan proyek otomat kecil yang terbatas, jadi infonya masih segar di pikiran saya =x

Jika saya tidak salah, pendekatan ini dapat dengan mudah membuat tipe yang sangat besar. Mempertimbangkan:

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

(ini mengasumsikan implementasi akan menggunakan tipe serikat pekerja. Ini dapat bekerja dengan implementasi yang berbeda/lebih kompleks)

Lucunya, inilah yang mungkin terjadi dengan tipe literal string template baru. Kasus ini dihindari dengan memiliki ambang batas untuk jenis serikat pekerja, tampaknya.

@AnyhowStep JS backreferences adalah satu-satunya produksi yang peka terhadap konteks (dan yang cukup sederhana dan terbatas pada saat itu - hanya hingga 9 grup yang dapat direferensikan seperti itu), dan tata bahasa regexp lainnya adalah reguler, jadi itu sebabnya saya curiga dapat diputuskan. Tapi terlepas dari itu, saya pikir kita bisa sepakat bahwa itu tidak praktis dalam arti kata apa pun. 🙂

Sunting: akurasi

Saya mengkonfirmasi komentar ini dari @rozzzly bekerja dengan TS 4.1.0 setiap malam!

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

Cobalah di taman bermain dan lihat bahwa fail memiliki kesalahan waktu kompilasi


Pembaruan : setelah bermain dengan fitur ini sedikit, itu tidak akan mencakup banyak kasus penggunaan. Misalnya, ini tidak berfungsi untuk string warna hex.

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

Hari ini, itu gagal dengan "Ekspresi menghasilkan tipe gabungan yang terlalu rumit untuk direpresentasikan.(2590)"

Saya mengkonfirmasi komentar ini dari @rozzzly bekerja dengan TS 4.1.0 setiap malam!

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

Cobalah di taman bermain dan lihat bahwa fail memiliki kesalahan waktu kompilasi

Ini akan memecahkan masalah data atau aria yang sebagian besar dari kita hadapi di perpustakaan UX jika dapat diterapkan ke indeks.

Pada dasarnya ini tetapi jelas itu tidak berfungsi karena TS hanya mengizinkan string | nomor. Karena ini pada dasarnya adalah string, dapatkah ini diaktifkan?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Update_: setelah bermain dengan fitur ini sedikit, itu tidak akan mencakup banyak kasus penggunaan. Misalnya, ini tidak berfungsi untuk string warna hex.

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

Hari ini, itu gagal dengan "Ekspresi menghasilkan tipe gabungan yang terlalu rumit untuk direpresentasikan.(2590)"

Ada beberapa referensi untuk batasan ini dalam catatan rilis. Ini membuat daftar semua kemungkinan kombinasi yang valid, dalam hal ini akan membuat gabungan dengan 16.777.216 (yaitu, 16^6) anggota.

Ini adalah ide bagus... Igmat membuat beberapa posting luar biasa di tahun 2016 yang terlihat bagus di atas kertas.

Saya menemukan ini karena saya ingin memastikan kunci objek literal yang diteruskan ke fungsi saya adalah nama kelas css yang valid. Saya dapat dengan mudah memeriksa saat runtime ... tetapi bagi saya tampaknya sangat jelas bahwa TypeScript harus dapat melakukan ini pada waktu kompilasi, terutama dalam situasi di mana saya hanya objek literal hard-coding dan TypeScript tidak harus mencari tahu apakah MyUnionExtendedExotictipe memenuhi SomeArbitraryRegexType.

Mungkin suatu hari nanti saya akan cukup berpengetahuan untuk memberikan kontribusi yang lebih produktif :/

Saya mengkonfirmasi komentar ini dari @rozzzly bekerja dengan TS 4.1.0 setiap malam!

Wow. Sejujurnya saya tidak berharap melihat ini diimplementasikan, setidaknya tidak dalam waktu dekat.

@chadlavi-casebook

Ada beberapa referensi untuk batasan ini dalam catatan rilis. Ini membuat daftar semua kemungkinan kombinasi yang valid, dalam hal ini akan membuat gabungan dengan 16.777.216 (yaitu, 16^6) anggota.

Saya ingin tahu seberapa besar persatuan itu sebelum menjadi masalah kinerja. Contoh @styfle menunjukkan betapa mudahnya mencapai langit-langit itu. Jelas akan ada tingkat pengembalian kegunaan yang semakin berkurang dari tipe kompleks vs kinerja.

@thehappycheese

Saya ingin memastikan kunci objek literal yang diteruskan ke fungsi saya adalah nama kelas css yang valid

Saya cukup yakin mengatakan bahwa itu tidak mungkin dengan implementasi saat ini. Jika ada dukungan untuk penghitung dan rentang, Anda mungkin akan mendapatkan validasi untuk nama kelas gaya BEM. Regex js standar untuk itu tidak _terlalu_ mengerikan:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
Anda juga akan membuang jangkar karena saat implementasinya berjalan, ini adalah kecocokan ujung-ke-ujung atau tidak sama sekali sehingga ^ dan $ tersirat. Nah, itu regex yang relatif sederhana untuk subset sempit dari pemilih css yang valid. Misalnya: ಠ_ಠ adalah nama kelas yang valid. Aku tidak bercanda.

Maafkan saya. Saya harus melakukan ini.

Saya menerapkan bahasa reguler di TypeScript.

Lebih tepatnya, saya menerapkan otomat terbatas deterministik sederhana menggunakan TS 4.1

Maksud saya, kita sudah bisa mengimplementasikan mesin Turing di TS. Jadi, DFA dan PDA "mudah", dibandingkan dengan itu.

Dan string template membuat ini lebih bermanfaat.


Tipe inti sebenarnya sederhana dan muat di <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>;

Ini menentukan otomat itulah bagian yang sulit.

Tapi saya cukup yakin seseorang dapat membuat regex ke generator TypeScript DFA™ ...


Saya juga ingin menyoroti bahwa contoh "string hex dengan panjang 6" menunjukkan Anda dapat membuat parameter fungsi hanya menerima string yang cocok dengan regex menggunakan peretasan yang jelek,

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

Berikut bonus Playground ; itu mengimplementasikan regex /^hello .*/

Dan Taman Bermain lainnya; itu mengimplementasikan regex / world$/

Satu contoh terakhir, Playground ; ini adalah regex string titik mengambang !

@AnyhowStep Yah saya menggunakan ide DFA Anda untuk menerapkan regex sederhana [abc]{4} yang berarti huruf abc dalam urutan apa pun dengan hilang tetapi persis panjang 4. (aaaa, abcc, bbcc, dll ...).
Tempat bermain

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

https://github.com/CyberZHG/toolbox

Jika saya memiliki lebih banyak kemauan, saya akan mengambil sesuatu seperti di atas dan menggunakannya untuk mengubah regex menjadi TS DFAs™ lol

Oke, saya baru saja membuat prototipe,

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

[Sunting] https://glitch.com/~efficacious-valley-repair <-- Ini menghasilkan output yang jauh lebih baik untuk regex yang lebih rumit

[Sunting] Sepertinya Glitch akan mengarsipkan proyek gratis yang tidak aktif terlalu lama. Jadi, inilah git repo dengan file,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Langkah 1, masukkan regex Anda di sini,
image

Langkah 2, klik konversi,
image

Langkah 3, klik URL taman bermain TS yang dihasilkan,
image

Langkah 4, gulir ke bawah hingga InLanguage_0 ,
image

Langkah 5, mainkan dengan nilai input,
image

image

Berteriak ke https://www.npmjs.com/package/regex2dfa , karena melakukan konversi yang berat

Jika seseorang membutuhkan sesuatu yang sedikit lebih kuat, inilah mesin Turing

Tempat bermain

Utas ini terlalu panjang untuk dibaca dan banyak komentar ditujukan oleh tipe literal templat atau di luar topik. Saya telah membuat edisi baru #41160 untuk diskusi tentang kasus penggunaan yang tersisa yang mungkin diaktifkan oleh fitur ini. Jangan ragu untuk terus mendiskusikan pengurai sistem tipe di sini

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

manekinekko picture manekinekko  ·  3Komentar

DanielRosenwasser picture DanielRosenwasser  ·  3Komentar

remojansen picture remojansen  ·  3Komentar

kyasbal-1994 picture kyasbal-1994  ·  3Komentar

siddjain picture siddjain  ·  3Komentar