Typescript: Предложение: тип строки, проверенный регулярным выражением

Созданный на 22 янв. 2016  ·  146Комментарии  ·  Источник: microsoft/TypeScript

Бывают случаи, когда свойство не может быть просто любой строкой (или набором строк), но должно соответствовать шаблону.

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

В JavaScript распространена практика хранения значений цвета в нотации css, например, в отражении в стиле css узлов DOM или различных сторонних библиотек.

Что вы думаете?

Literal Types Needs Proposal Suggestion

Самый полезный комментарий

Предложение по дизайну

Есть много случаев, когда разработчикам нужно больше заданного значения, чем просто строка, но они не могут перечислить их как объединение простых строковых литералов, например цветов CSS, электронных писем, номеров телефонов, ZipCode, расширений swagger и т. Д. Даже спецификация схемы json, которая обычно Используемый для описания схемы объекта JSON имеет свойства pattern и patternProperties, которые в терминах системы типов TS могут называться regex-validated string type и regex-validated string type of index .

Цели

Предоставьте разработчикам систему типов, которая на один шаг ближе к схеме JSON, которая обычно ими используется, а также не даст им забыть о проверках проверки строк, когда это необходимо.

Синтаксический обзор

Реализация этой функции состоит из 4 частей:

Тип, подтвержденный регулярным выражением

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;

Тип переменной, проверенный регулярным выражением

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

и то же самое, но более читабельное

let fontColor: CssColor;

Тип индекса, проверяемый регулярным выражением

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

и то же самое, но более читабельное

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

Защита типа для типа переменной

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

и то же самое

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

и использование определенного типа для лучшей читаемости

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

такой же как

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

Введите gurard для типа индекса

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

такой же как

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

и использование определенного типа для лучшей читаемости

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

такой же как

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

Семантический обзор

Задания

let email: Email;
let gmail: Gmail;
email = '[email protected]';// correct
email = '[email protected]';// correct
gmail = '[email protected]';// compile time error
gmail = '[email protected]';// correct
gmail = email;// obviously compile time error
email = gmail;// unfortunately compile time error too

К сожалению, из-за этой статьи мы не можем проверить, является ли одно регулярное выражение подтипом другого без резкого снижения производительности. Так что это должно быть ограничено. Но есть следующие обходные пути:

// explicit cast
gmail = <Gmail>email;// correct
// type guard
if (Gmail.test(email)) {
    gmail = email;// correct
}
// another regex subtype declaration
type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
gmail = email;// correct

К сожалению, присвоение переменной string переменной regex-validated также должно быть ограничено, потому что во время компиляции нет гарантии, что она будет соответствовать регулярному выражению.

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

Но мы можем использовать явные ограничения типов или типов, как показано здесь . Рекомендуется второй.
К счастью, это не относится к строковым литералам, потому что при их использовании мы можем проверить, соответствует ли его значение регулярному выражению.

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

Сужение типов для индексов

Для простых случаев regex-validated type индекса см. Тип gurard для типа индекса .
Но могут быть и более сложные случаи:

type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
interface UsersCollection {
    [email: Email]: User;
    [gmail: Gmail]: GmailUser;
}
let collection: UsersCollection;
let someEmail = '[email protected]';
let someGmail = '[email protected]';
collection['[email protected]'];// type is User
collection['[email protected]'];// type is User & GmailUser
collection[someEmail];// unfortunately type is any
collection[someGmail];// unfortunately type is any
// explicit cast is still an unsafe workaround
collection[<Email> someEmail];// type is User
collection[<Gmail> someGmail];// type is GmailUser
collection[<Email & Gmail> someGmail];// type is User & GmailUser

У литералов такой проблемы нет:

let collection: UsersCollection;
let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
collection[someEmail];// type is User
collection[someGmail];// type is User & GmailUser

Но для переменных лучшим вариантом является использование защиты типов, как в следующих более реалистичных примерах:

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

Но если мы будем использовать лучшее определение для типа Gmail него будет другое сужение типа:

type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is User & GmailUser
    }
}

Союзы и пересечения

На самом деле общие типы и типы regex-validated действительно разные, поэтому нам нужны правила, как правильно обрабатывать их объединения и пересечения.

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;// correct
type test_2 = Regex_1 & Regex_2;// correct
type test_3 = Regex_1 | NonRegex;// correct
type test_4 = Regex_1 & NonRegex;// compile time error
if (test_1.test(something)) {
    something;// type is test_1
    // something matches Regex_1 OR Regex_2
}
if (test_2.test(something)) {
    something;// type is test_2
    // something matches Regex_1 AND Regex_2
}
if (test_3.test(something)) {
    something;// type is Regex_1
} else {
    something;// type is NonRegex
}

Дженерики

Для дженериков нет особых случаев, поэтому тип regex-validated можно использовать с дженериками так же, как и обычные типы.
Для дженериков с ограничениями, подобными приведенным ниже, regex-validated type ведет себя как строка:

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

Обзор Emit

В отличие от обычных типов, regex-validated имеют некоторое влияние на emit:

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;
type test_2 = Regex_1 & Regex_2;
type test_3 = Regex_1 | NonRegex;
type test_4 = Regex_1 & NonRegex;
if (test_1.test(something)) {
    /* ... */
}
if (test_2.test(something)) {
    /* ... */
}
if (test_3.test(something)) {
    /* ... */
} else {
    /* ... */
}

будет компилироваться в:

var Regex_1 = / ... /;
var Regex_2 = / ... /;
if (Regex_1.test(something) || Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something) && Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something)) {
    /* ... */
} else {
    /* ... */
}

Обзор совместимости

Эта функция не имеет проблем с совместимостью, потому что есть только случай, который может ее нарушить, и он связан с тем, что тип regex-validated оказывает влияние на излучение в отличие от обычного типа, поэтому это действительный код TS:

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

когда кода ниже нет:

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

Но второй уже БЫЛ недействительным, но по другой причине (неверное объявление типа).
Итак, теперь мы должны ограничить объявление переменной с именем, совпадающим с типом, в случае, если этот тип regex-validated .

PS

Не стесняйтесь указывать на вещи, которые я, вероятно, упустил. Если вам нравится это предложение, я могу попробовать создать тесты, которые его охватывают, и добавить их в качестве PR.

Все 146 Комментарий

Да, я видел это прочесывание в DefininiteTyped,. Даже мы могли бы использовать что-то подобное с ScriptElementKind на уровне служб , где в идеале мы могли бы описать их как список определенных строк, разделенных запятыми.

Основные проблемы:

  • Непонятно, как их хорошо составить. Если мне нужен список разделенных запятыми "cat" , "dog" и "fish" , тогда мне нужно написать что-то вроде /dog|cat|fish(,(dog|cat|fish))*/ .

    • Если у меня уже есть типы, описывающие типы строковых литералов для "cat" , "dog"fish" , как мне интегрировать их в это регулярное выражение?

    • Очевидно, здесь есть повторение, что нежелательно. Возможно, исправление предыдущей проблемы упростит эту задачу.

  • Нестандартные расширения делают это ненадежным.

Огромный +1 к этому, ZipCode, SSN, ONet, многие другие варианты использования для этого.

У меня возникла та же проблема, и я вижу, что она еще не реализована, возможно, этот обходной путь будет полезен:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Как предложил @mhegazy, я помещу здесь свое предложение (# 8665). А как насчет разрешения простых функций проверки в объявлениях типов? Что-то подобное:

type Integer(n:number) => String(n).macth(/^[0-9]+$/)
let x:Integer = 3 //OK
let y:Integer = 3.6 //wrong

type ColorLevel(n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

type Hex(n:string) => n.match(/^([0-9]|[A-F])+$/)
let hexValue:Hex = "F6A5" //OK
let wrongHexValue:Hex = "F6AZ5" //wrong

Значение, которое может принимать тип, будет определяться типом параметра функции и самой оценкой функции. Это также решит проблему # 7982.

@rylphs +1 это сделало бы TypeScript чрезвычайно мощным

Как подтипы работают с _regex-проверенными строковыми типами_?

let a: RegExType_1
let b: RegExType_2

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

где RegExType_1 и RegExType_2 - строковые типы, проверенные _regex_.

Изменить: похоже, что эта проблема решается за полиномиальное время (см . Проблема включения для регулярных выражений ).

Также поможет TypeStyle: https://github.com/typestyle/typestyle/issues/5 : rose:

В JSX мы с @RyanCavanaugh видели, как люди добавляли aria- (и, возможно, data- ). Кто-то на самом деле добавил сигнатуру индекса строки в DefinitherTyped как универсальную. Для этого была бы полезна новая индексная подпись.

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

Предложение по дизайну

Есть много случаев, когда разработчикам нужно больше заданного значения, чем просто строка, но они не могут перечислить их как объединение простых строковых литералов, например цветов CSS, электронных писем, номеров телефонов, ZipCode, расширений swagger и т. Д. Даже спецификация схемы json, которая обычно Используемый для описания схемы объекта JSON имеет свойства pattern и patternProperties, которые в терминах системы типов TS могут называться regex-validated string type и regex-validated string type of index .

Цели

Предоставьте разработчикам систему типов, которая на один шаг ближе к схеме JSON, которая обычно ими используется, а также не даст им забыть о проверках проверки строк, когда это необходимо.

Синтаксический обзор

Реализация этой функции состоит из 4 частей:

Тип, подтвержденный регулярным выражением

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;

Тип переменной, проверенный регулярным выражением

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

и то же самое, но более читабельное

let fontColor: CssColor;

Тип индекса, проверяемый регулярным выражением

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

и то же самое, но более читабельное

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

Защита типа для типа переменной

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

и то же самое

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

и использование определенного типа для лучшей читаемости

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

такой же как

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

Введите gurard для типа индекса

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

такой же как

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

и использование определенного типа для лучшей читаемости

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

такой же как

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

Семантический обзор

Задания

let email: Email;
let gmail: Gmail;
email = '[email protected]';// correct
email = '[email protected]';// correct
gmail = '[email protected]';// compile time error
gmail = '[email protected]';// correct
gmail = email;// obviously compile time error
email = gmail;// unfortunately compile time error too

К сожалению, из-за этой статьи мы не можем проверить, является ли одно регулярное выражение подтипом другого без резкого снижения производительности. Так что это должно быть ограничено. Но есть следующие обходные пути:

// explicit cast
gmail = <Gmail>email;// correct
// type guard
if (Gmail.test(email)) {
    gmail = email;// correct
}
// another regex subtype declaration
type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
gmail = email;// correct

К сожалению, присвоение переменной string переменной regex-validated также должно быть ограничено, потому что во время компиляции нет гарантии, что она будет соответствовать регулярному выражению.

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

Но мы можем использовать явные ограничения типов или типов, как показано здесь . Рекомендуется второй.
К счастью, это не относится к строковым литералам, потому что при их использовании мы можем проверить, соответствует ли его значение регулярному выражению.

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

Сужение типов для индексов

Для простых случаев regex-validated type индекса см. Тип gurard для типа индекса .
Но могут быть и более сложные случаи:

type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
interface UsersCollection {
    [email: Email]: User;
    [gmail: Gmail]: GmailUser;
}
let collection: UsersCollection;
let someEmail = '[email protected]';
let someGmail = '[email protected]';
collection['[email protected]'];// type is User
collection['[email protected]'];// type is User & GmailUser
collection[someEmail];// unfortunately type is any
collection[someGmail];// unfortunately type is any
// explicit cast is still an unsafe workaround
collection[<Email> someEmail];// type is User
collection[<Gmail> someGmail];// type is GmailUser
collection[<Email & Gmail> someGmail];// type is User & GmailUser

У литералов такой проблемы нет:

let collection: UsersCollection;
let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
collection[someEmail];// type is User
collection[someGmail];// type is User & GmailUser

Но для переменных лучшим вариантом является использование защиты типов, как в следующих более реалистичных примерах:

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

Но если мы будем использовать лучшее определение для типа Gmail него будет другое сужение типа:

type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is User & GmailUser
    }
}

Союзы и пересечения

На самом деле общие типы и типы regex-validated действительно разные, поэтому нам нужны правила, как правильно обрабатывать их объединения и пересечения.

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;// correct
type test_2 = Regex_1 & Regex_2;// correct
type test_3 = Regex_1 | NonRegex;// correct
type test_4 = Regex_1 & NonRegex;// compile time error
if (test_1.test(something)) {
    something;// type is test_1
    // something matches Regex_1 OR Regex_2
}
if (test_2.test(something)) {
    something;// type is test_2
    // something matches Regex_1 AND Regex_2
}
if (test_3.test(something)) {
    something;// type is Regex_1
} else {
    something;// type is NonRegex
}

Дженерики

Для дженериков нет особых случаев, поэтому тип regex-validated можно использовать с дженериками так же, как и обычные типы.
Для дженериков с ограничениями, подобными приведенным ниже, regex-validated type ведет себя как строка:

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

Обзор Emit

В отличие от обычных типов, regex-validated имеют некоторое влияние на emit:

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;
type test_2 = Regex_1 & Regex_2;
type test_3 = Regex_1 | NonRegex;
type test_4 = Regex_1 & NonRegex;
if (test_1.test(something)) {
    /* ... */
}
if (test_2.test(something)) {
    /* ... */
}
if (test_3.test(something)) {
    /* ... */
} else {
    /* ... */
}

будет компилироваться в:

var Regex_1 = / ... /;
var Regex_2 = / ... /;
if (Regex_1.test(something) || Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something) && Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something)) {
    /* ... */
} else {
    /* ... */
}

Обзор совместимости

Эта функция не имеет проблем с совместимостью, потому что есть только случай, который может ее нарушить, и он связан с тем, что тип regex-validated оказывает влияние на излучение в отличие от обычного типа, поэтому это действительный код TS:

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

когда кода ниже нет:

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

Но второй уже БЫЛ недействительным, но по другой причине (неверное объявление типа).
Итак, теперь мы должны ограничить объявление переменной с именем, совпадающим с типом, в случае, если этот тип regex-validated .

PS

Не стесняйтесь указывать на вещи, которые я, вероятно, упустил. Если вам нравится это предложение, я могу попробовать создать тесты, которые его охватывают, и добавить их в качестве PR.

Я забыл указать на некоторые случаи пересечения и объединения типов regex-validated , но я описал их в последнем тестовом примере. Следует ли мне обновить Design proposal чтобы отразить это незначительное изменение?

@Igmat , вопрос по вашему предложению по дизайну: не могли бы вы выбросах ? Зачем нужно генерировать типы, проверенные регулярным выражением? Насколько я могу судить, другие типы не поддерживают проверки во время выполнения ... Я что-то упускаю?

@alexanderbird , да, любой другой тип не влияет на emit. Сначала я подумал, что regex-validated тоже подойдет, поэтому я начал создавать предложение и экспериментировать с предложенным синтаксисом.
Первый подход был таким:

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

и это:

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

Это нормально и не требует выдачи изменений, потому что "#000" можно проверить во время компиляции.
Но мы также должны обработать сужение с типа string до regex-validated type, чтобы сделать его полезным. Итак, я подумал об этом для обеих предыдущих настроек:

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

Таким образом, он также не влияет на emit и выглядит нормально, за исключением того, что регулярное выражение не очень читается и его нужно копировать во всех местах, поэтому пользователь может легко ошибиться. Но в этом конкретном случае это все же кажется лучше, чем изменение принципа работы type .
Но потом я понял, что это фигня:

let someString: string;
let email: /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/I;
if (/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(someString)) {
    email = someString; // Ok
}
email = someString; // compile time error

это кошмар. И это даже без пересечений и союзов. Поэтому, чтобы избежать подобных ситуаций, мы должны немного изменить type emit, как показано в предложении.

@DanielRosenwasser , не могли бы вы
Я действительно хочу помочь с реализацией этой функции, но это требует много времени ( tsc - действительно сложный проект, и мне все еще нужно работать над пониманием того, как он работает внутри), и я не знаю, это предложение готово к реализации, в противном случае вы отклоните эту реализованную таким образом функцию из-за видения дизайна на другом языке или по любой другой причине.

Привет, @Igmat , я думаю, мне нужно было сначала спросить о нескольких вещах

Для начала, я до сих пор не понимаю, зачем вам нужно какое-либо изменение для испускания, и я не думаю, что какой-либо вид испускания, основанный на типах, был бы приемлемым. Ознакомьтесь с нашими нецелями здесь .

Еще одна проблема, которую я должен был затронуть, - это проблема регулярных выражений, использующих обратные ссылки. Насколько я понимаю (и на собственном опыте), обратные ссылки в регулярном выражении могут заставить тест запускаться во времени экспоненциально по отношению к его входным данным. Это угловой случай? Возможно, но я бы предпочел избегать этого в целом. Это особенно важно с учетом того, что в сценариях редактора проверка типа в местоположении должна занимать минимальное количество времени.

Другая проблема заключается в том, что нам нужно либо полагаться на движок, на котором работает компилятор TypeScript, либо создавать собственный механизм регулярных выражений для выполнения этих вещей. Например, TC39 переходит к включению нового флага s чтобы . могло соответствовать символам новой строки. Было бы несоответствие между ESXXXX и более ранними средами выполнения, которые это поддерживают.

@igmat - у меня нет сомнений в том, что регулярные выражения, генерируемые во время выполнения, были бы полезны. Однако я не думаю, что они необходимы для того, чтобы эта функция была полезной (и, судя по тому, что сказал

Но мы также должны обработать сужение от строки к проверяемому регулярному выражению типу, чтобы сделать его полезным.

Я думаю, что это только в том случае, если мы хотим сузиться от динамической строки до типа, проверенного регулярным выражением. Это очень сложно. Даже в этом простом случае:

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

Мы не можем быть уверены, что типы будут совпадать - что, если число отрицательное? И по мере того, как регулярные выражения становятся более сложными, они становятся все более беспорядочными. Если бы мы действительно этого хотели, возможно, мы разрешим "интерполяцию типов: type Baz = /prefix:{number}/ ... но я не знаю, стоит ли туда идти.

Вместо этого мы могли бы частично достичь цели, если бы разрешили присваивать строковые литералы только типам, проверенным регулярным выражением.

Учтите следующее:

type Color = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
let foo: Color = '#000000';
let bar: Color = '#0000'; // Error - string literal '#0000' is not assignable to type 'Color'; '#0000' does not match /^#([0-9a-f]{3}|[0-9a-f]{6})$/i
let baz: Color = '#' + config.userColorChoice; // Error - type 'string' is not assignable to type 'regex-validated-string'

Как вы думаете, это работоспособная альтернатива?

@DanielRosenwasser , я внимательно прочитал Цели дизайна и, если я правильно вас понял, проблема заключается в нарушении нецелей №5.
Но мне это кажется не нарушением, а улучшением синтаксиса. Например, ранее у нас было:

const emailRegex = /.../;
/**
 * assign it only with values tested to emailRegex 
 */
let email: string;
let userInput: string;
// somehow get user input
if (emailRegex.test(userInput)) {
    email = userInput;
} else {
    console.log('User provided invalid email. Showing validation error');
    // Some code for validation error
}

После реализации этого предложения это будет выглядеть так:

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

Как видите, код почти такой же - это обычное простое использование регулярных выражений. Но второй случай гораздо более выразителен и предотвратит случайную ошибку пользователя, например, когда он забудет проверить строку перед назначением ее переменной, которая должна быть проверена регулярным выражением.
Во-вторых, без такого сужения типа мы не сможем нормально использовать проверенный регулярным выражением тип в индексах, потому что в большинстве случаев такие индексные поля работают с некоторой переменной, которая не может быть проверена во время выполнения, поскольку это может быть сделано с помощью литералов. .

@alexanderbird , я не предлагаю делать этот код действительным или добавлять какие-то скрытые проверки как во время выполнения, так и во время компиляции.

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

Этот код должен выдавать ошибку из-за моего предложения. Но это:

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

или это:

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

будет правильным и даже не повлияет на испускаемый код.

И, как я показал в предыдущем комментарии, литералов определенно не хватит даже для обычных случаев использования, потому что нам часто приходится работать с укусами из пользовательского ввода или из других источников. Без реализации этого воздействия излучения пользователям пришлось бы работать с этим типом следующим образом:

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

или для перекрестков:

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

Я не думаю, что заставлять пользователей дублировать код и использовать явное приведение, когда это может быть легко обработано компилятором, не является хорошим способом. Воздействие на выбросы действительно очень мало и предсказуемо, я уверен, что оно не удивит пользователей и не приведет к неправильному пониманию какой-либо функции или затрудненному обнаружению ошибок, в то время как реализация этой функции без выброса изменений определенно БУДЕТ.

В заключение хочу сказать, что простыми словами regex-validated type - это как переменная с ограниченной областью видимости, так и тип компилятора.

@DanielRosenwasser и @alexanderbird хорошо, у меня есть еще одна идея на этот

const type Email = /email-regex/;

В этом случае пользователь должен явно указать, что он / она хочет это как type и const , поэтому фактическая система типов не имеет изменений испускания, если она не используется с таким модификатором. Но если его использовать с ним, мы все равно сможем избежать множества ошибок, приведений и дублирования кода, добавив такой же выброс, как для:

const Email = /email-regex/;

Кажется, это даже больше, чем просто улучшение этого предложения, потому что это, вероятно, могло бы позволить что-то вроде этого (пример взят из проекта с

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

превращается в

export const type SOME_ACTION = 'SOME_ACTION';

Я пытался найти подобное предложение, но безуспешно. Если это может быть обходной путь, и если вам нравится такая идея, я могу подготовить проектное предложение и тесты для него.

@DanielRosenwasser , о вашей второй проблеме - я не думаю, что это когда-либо произойдет, потому что в моем предложении компилятор запускает регулярное выражение только для литералов, и не похоже, что кто-то сделает что-то вроде этого:

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

В любом случае мы могли бы проверить, как долго должен быть литерал, чтобы влиять на производительность в реальном времени, и создать некоторую эвристику, которая предупредит пользователя, если мы не сможем проверить его, пока он сталкивается с этими обстоятельствами в некоторых сценариях редактора, но мы бы проверили это, когда он будет компилировать проект. Или могут быть другие обходные пути.

Что касается третьего вопроса, я не уверен, что все правильно понимаю, но кажется, что движок регулярных выражений должен быть выбран в зависимости от target от tsconfig если они имеют разные реализации. Требуется дополнительное расследование.

@DanielRosenwasser есть какие мысли? 😄 О первоначальном предложении и о последнем. Может, стоит сделать более подробный обзор второго?

@Igmat Ваше предложение ограничивает валидацию только для строковых типов. Что вы думаете о предложении @rylphs ? Это позволит провести более общую проверку для всех примитивных типов:

type ColorLevel = (n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

Однако я подозреваю, что распространить этот механизм за пределы примитивов на непримитивные типы было бы слишком сложно.
Один момент, проблема, которую поднял

@zspitz это выглядит многообещающе, но, на мой взгляд, это может слишком сильно повлиять на производительность компилятора, потому что функция не ограничена никакими правилами и заставит TS вычислять некоторые выражения, которые слишком сложны или даже зависят от некоторых ресурсов, которые недоступны во время компиляции.

@Igmat

потому что функция не ограничена никакими правилами

У вас есть какие-то конкретные примеры? Возможно, можно ограничить синтаксис проверки «безопасным» / известным во время компиляции подмножеством Typescript.

как насчет того, чтобы охранники определяемого

// type guard that introduces new nominal type int
function isInt(value: number): value is type int { return /^\d+$/.test(value.toString()); }
// -------------------------------------^^^^ add type keyword here
function printNum(value: number) { console.log(value); }
function printInt(value: int) { console.log(value); }
const num = 123;
printNum(num); // ok
printInt(num); // error
if (isInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
}

@disjukr выглядит неплохо, но как насчет расширения типов?

Обязательно ли их расширять? Существует ли принцип дизайна TypeScript, который этого требует? В противном случае я бы предпочел номинальные типы, предлагаемые @disjukr, чем ничего, даже если они не расширяются.

Нам понадобится что-то довольно творческое, чтобы получить расширяемость. ИМХО - мы не можем определить, является ли одна защита произвольного типа (функция) подмножеством другой защиты произвольного типа.

Мы могли бы получить элементарную «расширяемость», используя менталитет утверждения типа (я не говорю, что это что-то «довольно творческое» - я говорю вот временный промежуток, пока кто-то не придумает что-то довольно креативное):

function isInt(value: number): value is type int { return /^\d+$/.test(value.toString()); }
// assert that biggerInt extends int. No compiler or runtime check that it actually does extend.
function isBiggerInt(value: number): value is type biggerInt extends int { return /^\d{6,}$/.test(value.toString()); }
// -----------------------------------------------------------^^^^ type extension assertion
function printNum(value: number) { console.log(value); }
function printInt(value: int) { console.log(value); }
function printBiggerInt(value: biggerInt) {console.log(value); }

const num = 123;
printNum(num); // ok
printInt(num); // error
printBiggerInt(num); // error
if (isInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
    printBiggerInt(num); // error
}
if (isBiggerInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
    printBiggerInt(num); // ok
}

Что может быть полезно, даже если это не звук. Но, как я сказал в начале, нужно ли нам, чтобы он был расширяемым, или мы можем реализовать его, как предлагает @disjukr? (В последнем случае я предлагаю реализовать его нерасширяемым способом @disjukr .)

Вид оффтопа, ответ на первый комментарий @DanielRosenwasser :
Для списка, разделенного запятыми, вы должны использовать привязки ^ и $ (это актуально в большинстве случаев, когда вы хотите проверить некоторую строку). И якоря помогают избежать повторения, для вашего примера регулярное выражение будет /^((dog|cat|fish)(,|$))+$/

Разрешить строковым типам быть регулярными выражениями /#[0-9]{6}/ и разрешить вложение типов в регулярные выражения ${TColor} :

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

Результат:

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

Пример использования: существует библиотека, предназначенная для написания «типобезопасных» стилей CSS в TypeScript typestyle . Предложенная выше функциональность могла бы очень помочь, поскольку библиотека должна предоставлять методы для использования во время выполнения, а предлагаемые строковые типы регулярных выражений вместо этого могли бы иметь возможность проверять тип кода во время компиляции и давать разработчикам отличный интеллект.

@DanielRosenwasser @alexanderbird @Igmat : ИМО, это предложение изменит правила игры для TypeScript и веб-разработки. Что в настоящее время мешает его реализации?

Я согласен с тем, что расширяемость и выделение типов не должны мешать остальным функциям. Если нет четкого пути по этим аспектам, реализуйте их позже, когда он появится.

Я прибыл сюда, так как мне нужен тип UUID, а не строка, поэтому в этом случае было бы здорово иметь регулярное выражение, определяющее строку + также был бы полезен способ проверки действительности типа (пример Email.test).

@skbergam Я еще раз пытаюсь реализовать это самостоятельно. Но проект TS действительно огромен, и у меня тоже есть работа, так что прогресса почти нет (мне удалось только создать тесты для этой новой функции). Если у кого-то больше опыта в расширении TS, любая помощь будет принята с благодарностью ...

Интересно, что это эффективно создает номинальный тип, поскольку мы не сможем установить какие-либо отношения подтипа / присваиваемости между любыми двумя неидентичными регулярными выражениями.

@RyanCavanaugh Ранее @maiermic прокомментировал

Изменить: похоже, что эта проблема решается за полиномиальное время (см. Проблема включения для регулярных выражений).

Но этого может быть недостаточно? Конечно, можно надеяться, что отношений регулярных выражений не так уж много, но вы никогда не узнаете.

Что касается проверки типов, если нам не нравится дублирование регулярных выражений, а typeof a const недостаточно (например, файлы .d.ts), как TS относится к valueof e , которое выдает буквальное значение e iff e является литералом, иначе возникает ошибка (и выдается что-то вроде undefined )?

@maxlk Также не по теме, но я взял ваше регулярное выражение и улучшил его, чтобы он не совпадал с конечными запятыми на допустимом вводе: /^((dog|cat|fish)(,(?=\b)|$))+$/ с тестом https://regex101.com/r/AuyP3g/1. Это использует положительный просмотр вперед для символа слова после запятой, заставляя до повторной проверки СУХИМ способом.

Привет!
Какой у этого статус?
Вы добавите эту функцию в ближайшее время? Ничего не могу найти по этому поводу в дорожной карте.

@lgmat Как насчет ограничения синтаксиса однострочными стрелочными функциями, используя только определения, доступные в lib.d.ts ?

Доступны ли эти потрясающие улучшения? Может, хотя бы в альфа-версии?

Проверенные типы регулярных выражений отлично подходят для написания тестов, проверка жестко запрограммированных входных данных была бы отличной.

+1. Наш вариант использования очень распространен, нам нужен строковый формат даты, например «дд / мм / ГГГГ».

хотя, как было предложено, это была бы очень крутая функция, у нее нет потенциала:

  • вывод всегда представляет собой жало (хотя время компиляции проверено), нет способа получить структурированный объект
  • грамматика ограничена тем, что могут делать регулярные выражения, и они не могут многое сделать, проблема регулярных выражений в том, что они регулярные, их результат - список, а не дерево, они не могут выражать произвольные длинные вложенные уровни
  • синтаксический анализатор должен быть выражен в терминах грамматики машинописного текста, которая ограничена и не расширяется

Лучшим способом будет внешний синтаксический анализ и передача в плагин, как предложено в # 21861, таким образом, все вышеперечисленное не является проблемой за счет более крутой кривой обучения, но эй! проверка регулярного выражения может быть реализована поверх этого, так что исходное предложение все еще остается в силе, выдвигаясь более продвинутой техникой

поэтому, как я уже сказал, более общим способом были бы собственные поставщики синтаксиса для любых литералов: # 21861

Примеры:

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

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

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


@lgmat Как насчет ограничения синтаксиса однострочными стрелочными функциями, используя только определения, доступные в lib.d.ts ?

@zspitz, что сделало бы многих недовольными, поскольку они увидели бы, что это возможно, но запрещено для них, в основном из соображений их безопасности.

Доступны ли эти потрясающие улучшения? Может, хотя бы в альфа-версии?

Насколько я знаю, для этого еще нужно предложение. @gtamas , @AndrewEastwood

Также я думаю, что на это повлияет # 11152.

@Igmat Ваше предложение ограничивает валидацию только для строковых типов. Что вы думаете о предложении @rylphs ? Это позволит провести более общую проверку для всех примитивных типов:

type ColorLevel = (n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

Однако я подозреваю, что распространить этот механизм за пределы примитивов на непримитивные типы было бы слишком сложно.

Основная проблема, которую я вижу в этом, - это проблемы безопасности, представьте себе какой-то вредоносный код, который будет использовать буферы для захвата памяти пользователя при проверке типа. Нам нужно было бы реализовать много песочницы вокруг этого. Я бы предпочел два разных решения, одно для строк и одно для чисел.

RegExp невосприимчив к этому для некоторых расширений, поскольку единственный способ использовать это злонамеренно - это сделать какое-то выражение с возвратом . При этом некоторые пользователи могут сделать это непреднамеренно, поэтому должна быть какая-то защита. Думаю, лучший способ сделать это - таймер.

Один момент, проблема, которую поднял

Это правда, это плохо, но мы можем решить эту проблему, указав, какая «современная» часть regExp нам нужна для нашей кодовой базы. По умолчанию используется обычное (это ES3?) Регулярное выражение, которое работает на каждом узле . И возможность включить новые флаги и утверждения просмотра назад.

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

Если пользователь отключил флаг с расширенными флагами.

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

TypeScript не оценил бы расширенный RegExp, если бы не сказал об этом. Но я бы посоветовал, чтобы он выдал предупреждение, объяснив, что происходит, и как включить расширенную проверку RegExp.

Если пользователь включил флаг с расширенными флагами и его узел поддерживает его.

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

Если пользователь включил флаг с расширенными флагами и его узел поддерживает его.

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

Я думаю, что это разумный способ сделать это.
Команды программистов обычно имеют одну и ту же версию NodeJS или легко могут обновиться, поскольку вся их кодовая база работает на кого-то с более новой версией.
Индивидуальные программисты могут легко адаптироваться на лету,

Каков текущий статус этой проблемы? Жалко видеть, что TypeScript обладает таким огромным потенциалом и десятками замечательных предложений, но они не получают особого внимания со стороны разработчиков ...

AFAIK первоначальное предложение было хорошим, если не считать обзора Emit, который не годится и в действительности не нужен, поэтому он не должен блокировать предложение.

Проблема, которую он пытается решить, может быть решена путем введения литералов регулярных выражений (что не должно быть сложно, поскольку они фактически эквивалентны строковым и числовым литералам) и оператора типа patternof (аналогично typeof и keyof ), которые будут принимать тип литерала регулярного выражения и возвращать _проверенный строковый_ тип. Вот как это можно использовать:

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 Я не думал о таком решении с оператором дополнительного типа, когда работал над первоначальным предложением.

Мне нравится этот подход к устранению воздействия испускания, вызванного типами, хотя он кажется более подробным.

И это привело меня к мысли, как расширить это предложение, чтобы пропустить добавление нового ключевого слова (как вы предлагаете) - ИМО, у нас уже есть довольно большое их количество, и мы не имеем влияния на излучение от системы типов (как в моем предложении).

Это займет 4 шага:

  1. добавить regexp-validated string literal type:
    TypeScript type Email = /some-long-email-regex/;
  2. Давайте изменим интерфейс RegExp в основной библиотеке на общий:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Вывод изменения типа для литералов регулярных выражений в реальном коде:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Добавьте помощник типа с помощью функции conditional types , например InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Пример использования:

const Email = /some-long-email-regex/;
type Email = ValidatedStringType<typeof Email>;

const email: Email = `[email protected]`; // correct
const email2: Email = `emexample.com`; // compile time error

let userInput: string;
if (Email.test(userInput)) {
    // `userInput` here IS of type `Email`
} else {
    // and here it is just `string`
}

@ Игмат Круто. Ваше предложение кажется более естественным для TypeScript и требует меньше изменений в компиляторе, что, вероятно, хорошо. Единственным преимуществом моего предложения было то, что литералы регулярных выражений будут ощущаться так же, как строковые и числовые литералы, что может сбивать с толку некоторых:

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

Но я думаю, что простота вашего предложения перевешивает один недостаток.

Изменить: мне не очень нравится длина ValidatedStringType<R> . Если бы мы решили вызвать проверенные строковые шаблоны , мы все-таки могли бы использовать PatternOf<R> . Я не говорю, что ваш тип печатает больше времени, большинство людей просто набирают первые три буквы и нажимают табуляцию. Это просто имеет большее влияние на разбиение кода.

@Igmat Ваше решение превосходно с точки зрения разработки, но с точки зрения удобочитаемости было бы гораздо лучше иметь возможность, предложенную @ m93a . Я думаю, что он может быть представлен внутри таким же образом, но он должен быть представлен пользователю как можно проще.

@Akxe Я не думаю, что разработчики один очень конкретный вариант использования.

@RyanCavanaugh Не могли бы вы рассказать нам свое мнение по этому исходное предложение и четыре последних комментария (исключая этот).) Спасибо! : +1:

как насчет того, чтобы иметь общий аргумент для строки, который по умолчанию равен .* ?

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

строковый литерал 'foo' можно рассматривать как сахар для string<foo>

Мне не очень нравится длина ValidatedStringType<R> . Если бы мы решили вызвать проверенные строки _patterns_, мы все-таки могли бы использовать PatternOf<R> .

@ m93a , IMO, в этом случае было бы лучше назвать их PatternType<R> чтобы они соответствовали уже существующим помощникам InstanceType и ReturnType .

@ amir-arad, интересно. Как в этом случае будет выглядеть interface RegExp ?

@RyanCavanaugh Я мог бы переписать исходное предложение новым способом, если это поможет. Нужно ли мне?

@ amir-arad Предлагаемый вами синтаксис противоречит остальной части TypeScript. Теперь вы можете передавать типы только как универсальный аргумент, а не как произвольное выражение. Предлагаемый вами синтаксис будет чрезвычайно запутанным.

Думайте об общих типах как о функциях, которые принимают тип и возвращают тип. Два следующих фрагмента кода очень близки как по значению, так и по синтаксису:

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

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

Ваш новый синтаксис похож на предложение, чтобы регулярное выражение в JavaScript было написано let all = String(.*) что было бы уродливым злоупотреблением синтаксисом вызова функции. Поэтому я не думаю, что ваше предложение имеет смысл.

@ m93a мое предложение

@ Игмат с макушки, как насчет:

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

@ amir-arad, извините, я не могу добавить более ценные детали к вашему предложению, но на первый взгляд это выглядит как очень существенное изменение для всего компилятора TS, потому что string - это очень простой примитив.

Несмотря на то, что я не вижу очевидных проблем, я думаю, что такое предложение должно быть намного более детальным и охватывать множество существующих сценариев плюс надлежащее обоснование его цели.
Ваше предложение добавляет один тип и изменяет один примитивный тип, в то время как мое предложение добавляет только один тип.

К сожалению, я не готов уделять много времени созданию предложения по такой функции (также вы можете заметить, что не все предложения в TS были реализованы без значительных задержек), но если вы над этим поработаете, я Буду рад предоставить вам свой отзыв, если это необходимо.

Если бы эти типы регулярных выражений были реальными регулярными выражениями (а не регулярными выражениями, подобными Perl, которые не являются регулярными), мы могли бы преобразовать их в детерминированный автомат и использовать для них декартово построение произведения, чтобы получить все конъюнкции и дизъюнкции. Регулярные выражения закрываются логическими операциями.

Кроме того, если бы строковые литералы не были атомарными, а были представлены в виде списков символов времени компиляции, это позволило бы реализовать все операторы в библиотеках. Это только немного ухудшит производительность.

Изменить: исправить ошибку.

Заметим, что Mithril действительно мог бы их использовать, а безопасность типов в общем случае почти невозможна без этого. Так обстоит дело как с гиперкриптом, так и с синтаксисом JSX. (Мы поддерживаем оба.)

  • У наших хуков жизненного цикла oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove и onremove есть свои собственные специальные прототипы.
  • Обработчики событий на виртуальных узлах DOM - это буквально все, что начинается с on , и мы поддерживаем как функции прослушивателя событий, так и объекты прослушивателя событий (с методами handleEvent ), выравниваясь с addEventListener и removeEventListener .
  • Мы поддерживаем ключи и ссылки по мере необходимости.
  • Все остальное рассматривается как атрибут или свойство, в зависимости от их наличия на самом поддерживающем узле DOM.

Таким образом, с подтвержденным регулярным выражением строковым типом + отрицанием типа мы могли бы сделать следующее для vnodes DOM:

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

    // Control attributes
    key: PropertyKey;
}

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

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

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

(Было бы также неплохо иметь возможность извлекать группы из таких регулярных выражений, но я не собираюсь задерживать дыхание на этом.)

Изменить: проясните несколько важных деталей в предложении.
Изменить 2: исправьте технический бит, чтобы он был математически точным.
Изменить 3: добавить поддержку общего пометки односимвольных союзов

Вот конкретное предложение, чтобы попытаться решить эту проблему гораздо более осуществимо: типы литералов шаблонов.

Кроме того, я считаю, что полные регулярные выражения, вероятно, не являются хорошей идеей, потому что их должно быть достаточно легко объединить с другими типами. Может быть, это могло бы быть лучше: типы литералов шаблона.

  • `value` - это буквально эквивалентно "value"
  • `value${"a" | "b"}` - это буквально эквивалентно "valuea" | "valueb"
  • `value${string}` - это функционально эквивалентно регулярному выражению /^value/ , но ему можно "value" , "valuea" и "valueakjsfbf aflksfief fskdf d" .
  • `foo${string}bar` - это функционально эквивалентно регулярному выражению /^foo.*bar$/ , но его немного проще нормализовать.
  • Конечно, может быть несколько интерполяций. `foo${string}bar${string}baz` - допустимый тип литерала шаблона.
  • Интерполяция должна расширять string , и она не должна быть рекурсивной. (Второе условие - по техническим причинам.)
  • Тип литерала шаблона A назначается типу литерала шаблона B тогда и только тогда, когда набор строк, присваиваемых A является подмножеством набора строк, присваиваемых B .

В дополнение к вышесказанному может существовать специальный тип starof T , где T должен состоять только из односимвольных строковых литералов. string будет существовать как псевдоним типа starof (...) , где ... - это объединение всех одиночных символьных строковых литералов UCS-2 от U + 0000 до U + FFFF, включая одиночный суррогаты. Это позволяет вам определить полную грамматику для числовых литералов ES base-10, например:

type DecimalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type Decimal = `${DecimalDigit}{starof DecimalDigit}`

type Numeric = `${(
    | Decimal
    | `${Decimal}.${starof DecimalDigit}`
    | `.${Decimal}`
)}${"" | (
    | `E${Decimal}` | `E+${Decimal}` | `E-${Decimal}`
    | `e${Decimal}` | `e+${Decimal}` | `e-${Decimal}`
)}`

Точно так же некоторые встроенные методы можно настроить для возврата таких типов:

  • Number.prototype.toString(base?) - может возвращать указанный выше тип Numeric или его вариант для статически известных base s.
  • +x , x | 0 , parseInt(x) и аналогичные - когда известно, что x является Numeric как определено выше, результирующий тип может правильно выводиться как буквальный числовой тип.

И, наконец, вы можете извлечь совпавшие группы следующим образом: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . Извлечение шаблона предполагает, что он всегда работает с полными именами, поэтому вы должны явно использовать интерполяцию ${string} для поиска произвольного включения. Это не жадный метод, поэтому ` "foo.bar.baz" extends $ {infer T}. $ {Infer U} ? [T, U] : never возвращает ["foo", "bar.baz"] , а не ["foo.bar", "baz"] .


С технической точки зрения это гораздо более осуществимо, чем необработанные регулярные выражения. Регулярные выражения JS даже не являются обычными - они становятся контекстно-зависимыми с обратными ссылками, и они связаны с большой сложностью в виде того, что до тех пор, пока вы блокируете рекурсию с их помощью, типы литералов шаблона генерируют каждый единственный регулярный язык , тот, который очень близко согласуется с основной теорией (но поддерживает только ее часть).

  • Пустой язык: ""
  • Союз: "a" | "b"
  • Конкатенация: `${a}${b}`
  • Звездочка Клини (частично): starof T ( T может содержать только отдельные символы и объединения.)

Это может сделать проверку подтипов строк подмножеством наихудшего сценария проблемы изоморфизма подграфов , но здесь есть несколько важных компенсирующих факторов:

  1. Распространенный случай - это объединения небольших конечных строк, которые можно моделировать с помощью деревьев. Это относительно очевидно для работы. (Я не рекомендую пытаться соединить их как веревки, поскольку это усложнит описанный выше алгоритм сопоставления, но вполне нормально нормализовать односимвольные объединения и тому подобное в одно соединение split +.)

  2. Вы можете смоделировать весь унифицированный тип как ориентированный граф, где:

    1. Помеченные звездочкой объединения таких символов - это подграфы, в которых родительский узел имеет ребра как для каждого символа, так и для каждого дочернего узла подграфа, и каждый символ имеет ребра как для всех других символов, так и для всех дочерних узлов подграфа.
    2. Остальная часть графа содержит ориентированную древовидную структуру, представляющую все другие возможности.

    Согласно этому чату Math.SE, в котором я был кратко (начиная примерно здесь ), я обнаружил, что этот результирующий граф будет иметь как ограниченный род (т.е. с конечным числом переходов через другие ребра *), так и без каких-либо starof типов, ограниченная степень. Это означает, что равенство типов сводит это к проблеме полиномиального времени и, если вы нормализуете объединения, это также не очень медленно, поскольку оно лишь немного быстрее, чем равенство деревьев. Я сильно подозреваю, что общий случай всего этого предложения (подмножество проблемы изоморфизма подграфов) также является полиномиальным временем с разумными коэффициентами. (В указанной выше статье Википедии есть несколько примеров в разделе «Алгоритмы» и ссылки, где может применяться специальный регистр.)


  3. Ни один из этих ключей вряд ли будет большим , поэтому большая часть фактических затрат времени выполнения здесь на практике амортизируется другими вещами. Пока это быстро для маленьких клавиш, этого достаточно.


  4. Все подграфы, которые будут сравниваться, имеют как минимум один узел: корневой узел. (Это представляет начало строки.) Таким образом, это значительно уменьшит проблемное пространство само по себе и гарантирует полиномиальную проверку времени.


И, конечно, пересечение между такими типами нетривиально , но я чувствую, что аналогичные компенсирующие факторы существуют просто из-за вышеуказанных ограничений. В частности, последнее ограничение делает очевидным полиномиальное время выполнения.

* Математически род определяется немного нелогично для нас, программистов (минимальное количество отверстий, которые нужно проткнуть в поверхности, чтобы нарисовать график без каких-либо скачков), но ограниченный род (ограниченное количество отверстий) подразумевает ограниченное количество переходов. .

Используя это конкретное предложение, вот как переводится мой пример из этого комментария :

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

    // Control attributes
    key: PropertyKey;
}

interface HTMLTypeMap {
    // ...
}

interface HTMLEventMap {
    // ...
}

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

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

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

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

Изменить: это также позволит правильно набирать 90% метода Lodash _.get и связанных с ним методов, используя его сокращенное обозначение свойств, например его метод _.property(path) и его сокращенное обозначение _.map(coll, path) . Наверное, есть несколько других, о которых я тоже не думаю, но это, наверное, самая большая из тех, о которых я могу думать. (Я собираюсь оставить реализацию этого типа в качестве упражнения для читателя, но могу заверить вас, что это возможно с помощью комбинации этого и обычного трюка условных типов с немедленно индексируемой записью, что-то вроде {0: ..., 1: ...}[Path extends "" ? 0 : 1] , чтобы обработать строку статического пути.)

Я рекомендую сосредоточить наши усилия на реализации поставщиков типов, которые можно использовать для реализации типов регулярных выражений.

Почему поставщики типов вместо непосредственной реализации типов регулярных выражений? Потому что

  1. Это более общее решение, которое добавляет много новых возможностей TypeScript, упрощая получение поддержки от более широкой группы разработчиков, помимо тех, кто видит значение в строковых типах регулярных выражений.
  2. Владельцы репозитория машинописных текстов, похоже, открыты для этой идеи и ждут подходящего предложения. См. № 3136

В F # есть поставщик типов регулярных выражений с открытым исходным кодом.

Некоторая информация о поставщиках типов: https://link.medium.com/0wS7vgaDQV

Можно представить, что после того, как поставщики типов реализованы, а поставщик типов регулярных выражений реализован как библиотека с открытым исходным кодом, можно было бы использовать его так:

type PhoneNumber = RegexProvider</^\d{3}-\d{3}-\d{4}$/>
const acceptableNumber: PhoneNumber = "123-456-7890"; //  no compiler error
const unacceptableNumber: PhoneNumber = "hello world"; // compiler error

@AlexLeung Я не уверен, что это правильный путь, по крайней мере, не для этого запроса.

  • TypeScript является структурно типизированным, а не номинально типизированным, и для манипуляций со строковыми литералами я хочу сохранить этот структурный дух. Тип провайдеры , как , что бы создать номинальные string подтип где RegexProvider</^foo$/> не будет рассматриваться как эквивалент "foo" , но номинальный подвид этого. Кроме того, RegexProvider</^foo$/> и RegexProvider</^fo{2}$/> будут рассматриваться как два разных типа, и я не фанат этого. Вместо этого мое предложение напрямую интегрируется со строками в их ядре, напрямую опираясь на теорию распознавания формального языка, чтобы гарантировать, что оно естественным образом вписывается.
  • С моим вы можете не только объединять строки, но и извлекать их части с помощью Key extends `on${infer K}` ? K : never или даже Key extends `${Prefix}${infer Rest}` ? Rest : never . Поставщики типов не предлагают эту функциональность, и нет ясного способа, как это сделать, если бы такая функциональность была добавлена.
  • Мой значительно проще на концептуальном уровне: я просто предлагаю добавить типы конкатенации строк и, для RHS условных типов, возможность извлекать обратное. Я также предлагаю интегрировать его с самим string , чтобы заменить регулярное выражение /.*/ . Он не требует изменений API, и кроме двух теоретически сложных частей, которые в основном отделены от остальной части кода, вычисление того, может ли тип литерала шаблона присваиваться другому, и извлечение фрагмента из строки аналогично, если не проще. , реализовать.

Кстати, в моем предложении тоже можно ввести этот пример PhoneNumber . Это немного более подробно, но я пытаюсь смоделировать данные, которые уже находятся в области TS, а не данные, которые существуют где-то еще (для чего наиболее полезны поставщики типов F #). (Стоит отметить, что технически это может быть расширено до полного списка возможных телефонных номеров здесь.)

type D = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type PhoneNumber = `${D}${D}${D}-${D}${D}${D}-${D}${D}${D}${D}`;

RegexProvider ^ foo $ /> и RegexProvider ^ fo {2} $ /> будут рассматриваться как два разных типа.

Поставщики типов могут потребовать реализации некоторого метода equals или compare , чтобы автор поставщика типа поставщика регулярного выражения мог определить, что оба приведенных выше случая являются эквивалентными типами. Автор поставщика типов может реализовать структурную или номинальную типизацию по своему усмотрению.

Возможно, можно было бы реализовать свой строковый литерал в качестве поставщика типа. Я не думаю, что синтаксис может быть таким же, но вы могли бы приблизиться к поставщику типов, который принимает переменное количество аргументов.

type D = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type PhoneNumber = StringTemplateMatcherProvider<D, D, D, "-", D, D, D, "-", D, D, D, D>;

@AlexLeung Но можно ли "123-456-7890" вашему типу? (Если так, это усложнит реализацию и сильно замедлит работу программы проверки.)

Что, если тип не имеет фиксированной длины (например, телефонный номер), наполовину связанный с обсуждаемым вопросом? Одна из ситуаций, когда я хотел бы использовать это в последнее время, - это сохранение имени комнаты в формате thread_{number} .

Регулярное выражение, соответствующее такому значению, - thread_[1-9]\d* . С тем, что предлагается, не представляется возможным (или даже возможным) сопоставить такой формат. В этой ситуации числовая часть значения может быть _любая_ длина больше нуля.

@jhpratt Я starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ , поскольку для этого требовалось лишь небольшое изменение. Он оптимизирует так же, как string оптимизирует как /^[\u0000-\uFFFF]*$/ , поэтому я решил продолжить и обобщить это.

Я не хочу расширять starof дальше этого, например, принимая произвольные нерекурсивные объединения, из-за проблем вычислительной сложности: проверка эквивалентности двух произвольных регулярных выражений * может выполняться в полиномиальном пространстве или полиномиальном времени ( преобразовать оба в минимальный DFA и сравнить - обычный способ, но очень медленно на практике), но оба способа очень медленны на практике, и, AFAICT, вы не можете использовать оба способа. Добавьте поддержку возведения в квадрат (например, a{2} ), и это в принципе невозможно (экспоненциальная сложность) . Это только для эквивалентности, и проверка того, соответствует ли регулярное выражение подмножеству строк, соответствует другому регулярному выражению, необходимое для проверки возможности присваивания, очевидно, будет еще более сложной.

* Регулярные выражения в математическом смысле: я включаю только одиночные символы, () , (ab) , (a|b) и (a*) , где a и b являются (потенциально разными) членами этого списка.

Вероятно, это глупый вопрос, но ... почему не так просто, при достаточных ограничениях, поддерживать функцию проверки (либо лямбда, либо именованную)?

Например, предположим, что мы используем ":", чтобы указать, что следующий элемент является валидатором (замените все, что хотите, на ":", если у вас есть мнение по этому поводу):

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

Вначале машинописный текст мог принимать только функции проверки, которые:

  • иметь единственный аргумент, который требуется / предполагается, что он имеет "базовый" тип
  • только ссылочные переменные, которые определены в функции валидатора
  • вернуть значение (которое будет принудительно переведено в логическое значение в процессе проверки)

Вышеупомянутые условия кажутся легко проверяемыми компилятору машинописного текста, и как только эти условия будут приняты, большая часть сложности реализации исчезнет.

Кроме того, если необходимо ограничить начальную область действия управляемым размером:

  • функции проверки могут быть добавлены только к подмножеству собственных типов (строка, число)

Я не думаю, что это последнее ограничение было бы всем необходимым, но если есть какие-либо вопросы относительно того, будет ли оно так, я также не думаю, что стоит тратить много времени на его обсуждение, потому что решение с указанным выше ограничением по-прежнему будет решать огромный спектр реальных вариантов использования. Вдобавок я вижу небольшой недостаток вышеупомянутых ограничений, потому что их ослабление позже было бы простым и естественным расширением, которое не потребовало бы изменений в основном синтаксисе и просто расширило бы широту поддержки языка компилятором.

@mewalig Это будет означать, что что-то похожее на функцию времени выполнения на самом деле будет выполняться не могли получить доступ к чему-либо из среды выполнения (переменным, функциям), что было бы довольно неудобно.

Кроме того, вы обычно не хотите, чтобы компилятор запускал что-либо, что вы ему бросаете, особенно плохо оптимизированные функции или явно злонамеренные while(true){} . Если вам нужно метапрограммирование, вы должны продумать его дизайн. Просто случайное разрешение выполнения кода времени выполнения во время компиляции было бы «способом PHP» сделать это.

Наконец, предложенный вами синтаксис переключает обычный шаблон

let runtime: types = runtime;

(т.е. типы после двоеточия) наизнанку, фактически являясь

type types = types: runtime;

что ужасно. Так что спасибо за ваше предложение, но это определенно плохая идея.

Эти функции не могли получить доступ к чему-либо из среды выполнения (переменным, функциям), что было бы довольно неудобно.

Конечно, могли , если компилятор имеет доступную среду выполнения ECMAScript ( tsc делает, кстати!). Очевидно, у вас есть проблема двусмысленности с семантикой времени компиляции, например, fetch() против семантики времени выполнения, но это итерация.

Просто случайное разрешение выполнения кода времени выполнения во время компиляции было бы «способом PHP» сделать это.

Это очень похоже на функции C ++ constexpr , которые подходят. Решение состоит в том, чтобы сказать, что constexpr может использовать только constexpr , но все может использовать constexpr . Тогда у вас могут быть constexpr -эквивалентные версии файловой системы для файловой системы времени компиляции, которые могут быть довольно мощными.

Синтаксис мне тоже кажется примерно прекрасным: LHS - это тип, конечно, RHS тоже какой-то тип. Моя проблема больше связана с тем, как бы вы составили типы за пределами «базового» типа, но это тоже разрешимо.

Так что спасибо за ваше предложение, но это определенно плохая идея.

Это может оказаться плохой идеей, но сейчас я просто вижу очень недооцененную идею, которая, вероятно, потребует слишком большого отхода от целей машинописного текста. Это не значит, что не может быть хорошей идеи, похожей на нее!

Обсуждение этой функции, похоже, на данный момент остановлено ( PR закрыт, и, согласно команде разработчиков заметок, _не хотят делать это до тех пор, пока у нас не появятся номинальные типы и обобщенные подписи индексов, и мы должны знать, как они выглядят. _).

В любом случае, я хочу предложить еще одно гипотетическое расширение текущего PR, которое поддерживало бы извлечение шаблонов регулярных выражений ( @isiahmeadows представил свое собственное предложение, но, честно говоря, я не могу сейчас об этом

Мне очень нравится нынешний пиар, и я буду строить на нем свое предложение. Я хотел бы предложить синтаксис, основанный на выводе аргументов универсального типа, который у нас есть для функций (и условных типов с ключевым словом infer ). Просто потому, что у людей уже есть некоторая интуиция, что в универсальной функции вы можете «извлекать» типы из переданных буквальных объектов.

Например у нас есть такой тип.

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

и мы можем использовать этот тип для проверки литеральных типов

const goodLiteral = "foo.bar";
const badLiteral = "foo";
const regextTest: Prop1 = goodLiteral; //no error
const regextTest: Prop1 = badLiteral; //compiler error

function funProp1(prop: Prop1) { } 

funProp1(goodLiteral); //no error
funProp1(badLiteral); //error

Однако, когда мы используем тип Regex в параметре функции, мы можем использовать синтаксис угловых скобок, чтобы обозначить, что мы хотим вывести совпадающие строки. Например

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

const goodLiteral = "foo.bar";
const badLiteral = "foo";

function funProp1<M1 extends string, M2 extends string>(prop: Prop1<M1, M2>) : [M1, M2] 
{
    const m = prop.match(Prop1);
    return [m[1], m[2]];
} 

const res1 = funProp1(goodLiteral); //no error. Function signature inferred to be funProp<"foo", "bar">(prop: Prop1<"foo", "bar">) : ["foo", "bar"]
const res2 = funProp1(badLiteral); //compiler error

обратите внимание, что предполагаемый тип res1 - ["foo", "bar"]

Это полезно?

  1. Ember.js / функция получения lodash

Вы можете реализовать типобезопасный метод получения "строкового пути", чтобы этот код работал:

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

Но, вероятно, потребуется решить эту проблему, если мы хотим избежать множества перегрузок для фиксированного максимального числа возможных "глубины" get .

  1. Используйте извлеченные параметры в сопоставленных типах.

Например, если бы мы могли сделать что-то вроде этого https://github.com/Microsoft/TypeScript/issues/12754. Тогда у нас может быть возможность отменить функцию (убрать некоторый префикс / суффикс из всех свойств данного типа). Вероятно, потребуется ввести некоторую более обобщенную форму сопоставленного типизированного синтаксиса для выбора нового ключа для свойства (например, синтаксис типа { [ StripAsyncSuffix<P> for P in K ] : T[P] } , кто-то уже предлагал что-то подобное)

Возможно, будут и другие варианты использования. Но я думаю, что большинство из них подошло бы к этим двум типам (1. определение правильного типа на основе предоставленного строкового литерала, 2. преобразование имен свойств входного типа в новые имена свойств нового определенного типа)

Это то, что мы могли бы сделать.

В настоящее время я создаю собственные правила lint, чтобы иметь возможность проверять URL-адреса - хотя это было бы намного проще, если бы мы могли определить необязательные параметры, для которых требуется регулярное выражение, чтобы иметь возможность проверять наши идентификаторы.

В общем, это даст нам гораздо больше возможностей для подтверждения достоверности свойств в нашей кодовой базе.

Есть ли какие-либо изменения в поставщиках типов, строковых литералах шаблонов или других предложениях? Это был бы такой отличный инструмент.

Мой обходной путь для этого в настоящее время является использование интерфейса маркера , как это .

interface TickerSymbol extends String {}

Единственная проблема в том, что когда я хочу использовать его в качестве ключа индекса, я должен преобразовать его в string .

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

Однако, похоже, JavaScript подходит для типа индекса String (с заглавной S).

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

Мы рассмотрели вышеуказанные предложения и у нас есть вопросы и комментарии.

Проблемные аспекты предложений на данный момент

Типы, создающие излучение

Мы стремимся к тому, чтобы система типов была полностью стерта, поэтому предложения, требующие псевдонимов типов для создания генерируемого кода, выходят за рамки. Я выделю несколько примеров в этой ветке, где это произошло, возможно, неочевидным образом:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -220180091 - создает функцию и тип одновременно

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - также делает это

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
// ... later
setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (CssColor.test(color)) {
    //  ^^^^^^^^ no value declaration of 'CssColor' !
        fontColor = color;// correct
    }
}

Повторюсь: это не стартер . Типы в TypeScript можно компоновать, и создание JS из типов в этом мире невозможно. У самого длинного предложения на сегодняшний день есть обширные типы выбросов; это не работает. Например, для этого потребуется обширное излучение, ориентированное на тип:

type Matcher<T extends number | boolean> = T extends number ? /\d+/ : /true|false/;
function fn<T extends number | boolean(arg: T, s: Matcher<T>) {
  type R = Matcher<T>
  if (R.test(arg)) {
      // ...
  }
}
fn(10, "10");
fn(false, "false");

Запреты на перекрестках

На самом деле общие типы и типы, проверенные регулярным выражением, действительно разные, поэтому нам нужны правила, как корректно обрабатывать их объединения и пересечения.

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

TypeScript не может ошибаться при создании экземпляров пересечений, поэтому это не будет частью окончательного дизайна.

Эргономика

В целом, наш самый важный вывод заключается в том, что нам нужно что-то, где вы не пишете один и тот же RegExp дважды (один раз в пространстве значений, один раз в пространстве типов).

Учитывая вышеупомянутые опасения по поводу типа emit, наиболее реалистичным решением является запись выражения в пространстве значений:

// Probably put this in lib.d.ts
type PatternOf<T extends RegExp> = T extends { test(s: unknown): s is infer P } ? P : never;

const ZipCode = /^\d\d\d\d\d$/;
function map(z: PatternOf<typeof ZipCode>) {
}

map('98052'); // OK
map('Redmond'); // Error

Конечно, вы все равно можете написать RegExp в пространстве типов, но валидация во время выполнения будет недоступна, и любое нелитеральное использование потребует повторного тестирования или утверждения:

function map(z: /^\d\d\d\d\d$/) { }
map('98052'); // OK
map('Redmond'); // Error

function fn(s: string) {
    map(s); // Error
    // typo
    if (/^\d\d\d\d$/.test(s)) {
        // Error, /^\d\d\d\d$/ is not assignable to /^\d\d\d\d\d$/
        map(s);
    }

    if (/^\d\d\d\d\d$/.test(s)) {
        // OK
        map(s);
    }
}

Сбор и разъяснение вариантов использования

Для нового типа шрифта мы в идеале хотели бы увидеть несколько примеров, в которых:

  • У решаемой проблемы нет лучшей альтернативы (включая правдоподобные альтернативы, которых еще нет на языке).
  • Проблема возникает со значимой частотой в реальных кодовых базах.
  • Предлагаемое решение хорошо решает эту проблему.

Проверка литералов во время компиляции

Эта ветка подразумевает множество вариантов использования; конкретные примеры были реже. К сожалению, многие из этих примеров кажутся неполными - в них используется регулярное выражение, которое отклоняет допустимые входные данные.

  • Цвет шрифта - AFAIK все, что принимает шестнадцатеричные цвета, также принимает, например, «белый» или «небесно-голубой». Это также неправильно отклоняет синтаксис rgb(255, 0, 0) .
  • SSN, Zip и т. Д. - Хорошо, но почему в вашем коде есть буквальные SSN или почтовые индексы? Действительно ли это необходимость в номинальных типах? Что произойдет, если у вас есть подкласс строк, который нельзя точно описать с помощью RegExp? См. «Конкурирующие предложения»

    • Целое число - неверно отклоняет "3e5"

    • Электронная почта - обычно считается плохой идеей . Опять же, в вашем коде есть строковые литералы адреса электронной почты?

    • Спецификации CSS Border - я мог поверить, что отдельная библиотека может предоставить точный RegEx для описания DSL, который она сама поддерживает

    • Написание тестов - здесь есть смысл жестко запрограммированные входные данные, хотя это почти противоположность, потому что ваш тестовый код, вероятно, должен предоставлять много недопустимых входных данных.

    • Форматы даты - как / почему? Date есть конструктор для этого; если ввод поступает извне среды выполнения, вам просто нужен номинальный тип

    • URI - вы можете представить, что fetch указывает host на отсутствие с http(s?):

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

Одна из проблем - это «прецизионность» - что происходит, когда кто-то услужливо появляется в DefininiteTyped и добавляет типы RegExp к каждой функции в библиотеке, тем самым нарушая каждый небуквенный вызов? Хуже того, авторам файла определения придется точно согласовывать с потребителями его «правильное написание» проверочного RegExp.
Похоже, это быстро ставит нас на путь к ситуации с Вавилонской башней, когда каждая библиотека имеет свою собственную версию того, что квалифицируется как URL, что квалифицируется как имя хоста, что квалифицируется как электронная почта и т. Д., И любой, кто соединяет две библиотеки должен вставлять утверждения типа или копировать регулярные выражения, чтобы удовлетворить компилятор.

Обеспечение выполнения проверок во время выполнения

Было некоторое обсуждение проверок, когда мы хотим убедиться, что аргументы функции были проверены предыдущим регулярным выражением, например fn в предыдущем разделе « Эргономика ». Это кажется простым и полезным, если регулярное выражение, требующее проверки, хорошо известно. Однако это большое «если» - насколько я помню, я не могу вспомнить ни одной библиотеки, которая предоставляет проверочные регулярные выражения. Он может предоставлять функции проверки, но это означает, что предоставляемая функция относится к номинальным или помеченным типам, а не к типам регулярных выражений.

Противоречие этой оценке приветствуется.

Ключи свойств / Индексаторы строк регулярных выражений

Некоторые библиотеки обрабатывают объекты в соответствии с именами свойств. Например, в React мы хотим применить типы к любой опоре, имя которой начинается с aria- :

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

По сути, это ортогональная концепция (мы могли бы добавлять типы Regex без добавления ключей свойств Regex, и наоборот).

TODO (я или кто угодно): Откройте для этого отдельный вопрос.

Конкурирующие предложения

Номинальные или маркированные типы

Допустим, у нас были какие-то номинальные / помеченные типы:

type ZipCode = make_unique_type string;

Затем вы можете написать функцию

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

На этом этапе действительно ли вам нужны типы RegExp? Обратитесь к разделу проверки «во время компиляции» для получения дополнительных сведений.

И наоборот, допустим, у нас есть типы RegExp, а не номинальные типы. Возникает соблазн начать (ab) использовать их для сценариев без проверки:

type Password = /(IsPassword)?.*/;
type UnescapedString = /(Unescaped)?.*/;
declare function hash(p: Password): string;

const p: Password = "My security is g00d"; // OK
const e: UnescapedString = "<div>''</div>"; // OK
hash(p); // OK
hash(e); // Error
hash("correct horse battery staple"); // OK

Распространенной вещью в потоке является то, что эти регулярные выражения могут помочь проверить тестовый код, потому что даже если в производственных сценариях код будет работать со строками, предоставленными во время выполнения, а не с жестко закодированными литералами, вам все равно понадобится некоторая проверка того, что ваши тестовые строки были " верный". Это может показаться аргументом для номинальных / помеченных / брендированных строк, хотя, поскольку вы в любом случае пишете функцию проверки, а преимущество тестов состоит в том, что вы знаете, что они выполняются исчерпывающе (таким образом, любые ошибки во входных данных теста будут быть отмеченным в начале цикла разработки).

Не проблемы

Мы обсудили следующие аспекты и считаем их не блокирующими.

Возможности хоста

Новые среды выполнения поддерживают больший синтаксис RegExp, чем старые среды выполнения. В зависимости от того, где выполняется компилятор TypeScript, определенный код может быть действительным или недопустимым в зависимости от возможностей среды выполнения по синтаксическому анализу новых функций RegExp. На практике большинство новых функций RegExp довольно эзотеричны или относятся к групповому сопоставлению, что, похоже, не согласуется с большинством вариантов использования здесь.

Представление

RegExes могут выполнять неограниченный объем работы, а сопоставление с большой строкой может выполнять сколь угодно большой объем работы. Пользователи уже могут сами выполнять DOS другими способами и вряд ли напишут злонамеренно неэффективный RegExp.

Подтип ( /\d*/ -> /.*/ ?), Объединение, пересечение и непригодность для проживания

Теоретически /\d+/ - это познаваемый подтип /.+/ . Предположительно существуют алгоритмы, позволяющие определить, соответствует ли одно RegExp чистому подмножеству другого (при определенных ограничениях), но, очевидно, потребует синтаксического анализа выражения. На практике мы на 100% согласны с тем, что регулярные выражения не формируют неявные отношения подтипов на основе того, что они соответствуют; это, наверное, даже предпочтительнее.

Операции объединения и пересечения будут работать "из коробки", если отношения назначаемости определены правильно.

В TypeScript, когда два примитивных типа «сталкиваются» на пересечении, они уменьшаются до never . Когда два регулярных выражения пересекаются, мы просто сохраняем это как /a/ & /b/ а не пытаемся создать новое регулярное выражение, соответствующее пересечению двух выражений. Никакого сокращения до never не будет, нам понадобится алгоритм, чтобы доказать, что ни одна строка не может удовлетворить обе стороны (это проблема, параллельная описанной ранее re: subtyping).

Следующие шаги

Подводя итог, следующие шаги:

  • Отправить отдельную проблему для ключей свойств с именем Regex AKA индексаторы строк регулярных выражений
  • Получите конкретные и правдоподобные варианты использования для проверки строковых литералов во время компиляции

    • Пример: определите функции в DefinuneTyped или других библиотеках, которым это принесет большую пользу.

  • Понять, являются ли номинальные / маркированные / фирменные типы более гибким и широко применимым решением для небуквальной проверки
  • Определите библиотеки, которые уже предоставляют проверочные регулярные выражения

Пример использования: функции типа Hyperscript (https://github.com/hyperhype/hyperscript)
Функция гиперкрипта обычно называется как h('div#some-id')
Средство сопоставления шаблонов в стиле регулярных выражений позволило бы определить тип возвращаемого значения h которым в данном примере будет HTMLDivElement.

Если бы система типов могла добавлять строковые литералы, то практически любое свойство CSS могло бы быть типобезопасным.

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

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

Селекторы CSS также могут быть проверены ( element.class#id - допустимо, div#.name - недействительно)

Если бы захват групп работал (каким-то образом), то метод Lodash get мог бы быть типобезопасным.

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

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

Это тоже может быть вещь:

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

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

Вариант использования: функции, подобные гиперкрипту (гипергайп / гиперкрипт)

Как будет выглядеть это регулярное выражение или какую проверку оно обеспечит? Это для перегрузки функции на основе регулярных выражений?

FWIW Библиотека принимает имена тегов с именами, а также работает с произвольными именами тегов.

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

Он также допускает неограниченное смешивание значений класса и идентификатора.

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

Селекторы CSS тоже могут быть проверены

Селекторы CSS не могут быть проверены с помощью регулярного выражения

Как будет выглядеть это регулярное выражение или какую проверку оно обеспечит? Это для перегрузки функции на основе регулярных выражений?

Не OP, но я полагаю, да, что-то вроде перегрузки HTMLDocument#createElement() , например:

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

Я уверен, что RE неполные. Обратите внимание, что это частный случай проверки селекторов CSS, которые используются во многих контекстах обычным образом. Например, для HTMLDocument.querySelector() совершенно нормально вернуть HTMLElement в качестве запасного варианта, если вы используете сложный селектор.

Мне любопытно, есть ли примеры без перегрузки, которые возможны и полезны.

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

Мой вариант использования - это тот, который я объяснил в этом комментарии в библиотеке CCXT, где у меня есть строки, представляющие TickerSymbol s. Мне все равно, проверяются ли они на соответствие шаблону регулярного выражения, но я хочу, чтобы они рассматривались как подтипы string поэтому я получаю более строгие назначения, проверку типов параметров и т. Д. Я обнаружил, что это быть очень полезным, когда я занимаюсь функциональным программированием, с этим я могу легко отслеживать TickerSymbols, Currencies, Assets и т. д. во время компиляции, где во время выполнения они являются просто обычными строками.

@omidkrad Похоже, вам нужны номинальные типы , а не

@ m93a В моем случае я буду в порядке с номинальными типами, но для того же случая использования вы можете использовать типы, проверенные регулярным выражением, для более строгой проверки типов и самодокументирования строковых типов.

Селекторы CSS тоже могут быть проверены

Селекторы CSS не могут быть проверены с помощью регулярного выражения

Что ж, если бы регулярное выражение позволило нам сшить их вместе, мы могли бы копировать регулярные выражения CSS ..., верно?

(Черновая версия) типизированной объектной модели CSS

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

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

Потенциально устраняет желание использовать модель CSS с строгой типизацией.

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

@RyanCavanaugh В частности, для Mithril имя тега извлекается через группу захвата в ^([^#\.\[\]]+) (по умолчанию "div" ), но для наших целей будет достаточно сопоставления ^(${htmlTagNames.join("|")}) . Итак, используя мое предложение , этого было бы достаточно для моих целей:

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

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

Что касается событий и атрибутов, мы могли бы переключиться на эту некогда отрицаемую землю типов:

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

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

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

Кстати, эта бесшовная интеграция и избежание сложности - вот почему я все еще предпочитаю свое предложение буквальным регулярным выражениям.


Однако я не знаю, как это сделать с чистыми типами регулярных выражений. Я хочу указать на это.

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

bent имеет другой тип возврата, основанный на том, что задано как строка, описывающая ожидаемый тип ответа, например

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

Он также принимает некоторые другие аргументы, такие как метод и URL-адрес, в виде строк, но они могут отображаться в любой позиции, поэтому, если мы попытаемся использовать объединения для описания всего возвращаемого типа ( 'json' | 'buffer' | 'string' ), это вместо этого будет глупо. просто string сочетании с типами URL и метода в объединении, что означает, что мы не можем автоматически вывести тип возвращаемого значения на основе типа, указанного в первом вызове.

@Ovyerus, как вам здесь помогут типы регулярных выражений? Что бы вы ожидали написать? Вы можете смоделировать что-то похожее на поведение bent с помощью перегрузок или условных типов.

type BentResponse<Encoding> = Promise<
    Encoding extends "json" ? MyJsonType :
    Encoding extends "buffer" ? Buffer | ArrayBuffer :
    Encoding extends "string" ? string :
    Response
>;

declare function bent<T extends string>(urlOrEncoding: T): (url: string) => BentResponse<T>;

http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAQhB2wBKEDOYD29UQDwFF4BjDAEwEt4BzAPigF4oAFAJwwFtydcBYAKCiCohEhWpQIAD2AJSqKACIAVqiwKoAfigBZEAClV8ACrhoALn5DhxMpSoTps + QoBGAVwBmHiC3VaYnt4sUAA + UACCLCwAhiABXj5QFgJCIrbiUjLwcoqowCx2flB5BeLJVijoWDj8NADc-PykEEQANtEs0B5uxMDkWFAuCMC4Rg5ZOSV2NAAUbiytAPIsaWJUZlBGAJQbcwsbU9RbDHRwiJWY2HhG9Y18lDIsHtFE0PFBUADeUAD67gksDbReAgKAAX34Dx8z1eOn0hhMkC + vxUWCBIPBdxm0VQIGIUBmx3odE + liErQgwCgkg2ugMWER0EY0QA7tFyFShogZspDAotjyABbAYBgVBmAD0Eqk0XYYApADoSOx + Q0 + GCBVsgA

О, мне было непонятно, извините, я считаю, что моя проблема была больше связана с сопоставлением http(s): в начале строки для определения базового URL.

Подпись Бента больше похожа на

type HttpMethods = 'GET' | 'PATCH' | ...
type StatusCode = number;
type BaseUrl = string; // This is where I would ideally need to see if a string matches http(s):
type Headers = { [x: string]: any; };

type Options = HttpMethods | StatusCode | BaseUrl | Headers;

function bent(...args: Options[]): RequestFunction<RawResponse>
function bent(...args: (Options | 'json')[]): RequestFunction<JSON>
// and so on

Однако наличие BaseUrl в виде строки поглощает HttpMethods и объединения типов возвращаемых значений, что в конечном итоге составляет всего лишь string . Наличие его в виде строки также «неправильно» соответствует принципу работы bent, поскольку он проверяет наличие ^http: или ^https: , чтобы определить, что следует использовать в качестве базового URL-адреса.

Если бы у нас были типы регулярных выражений, я мог бы определить BaseUrl как type BaseUrl = /^https?:/ , и в идеале это позволило бы правильно проверять строки, которые не являются методом HTTP или кодировкой ответа, а также не поглощать их в string тип.

Собственно, я такой же.

-
Прокоп Симек

20 октября 2019 г., 03:23:30, Майкл Митчелл ([email protected])
написал:

О, мне было непонятно, извините, я считаю, что моя проблема была больше похожа на
соответствие http (s): в начале строки для определения базового URL.

Подпись Бента больше похожа на

введите HttpMethods = 'GET' | «ПАТЧ» | ... тип StatusCode = число; тип BaseUrl = строка; // Здесь мне в идеале нужно проверить, соответствует ли строка http (s): type Headers = {[x: string]: any; };
введите Options = HttpMethods | StatusCode | BaseUrl | Заголовки;
function bent (... args: Options []): RequestFunctionfunction bent (... args: (Параметры | 'json') []): RequestFunction// и так далее

Однако наличие BaseUrl в виде строки поглощает HttpMethods и возвращает
объединение типов, которое заканчивается просто строкой. Имея это просто как строку
также "неправильно" соответствует принципу работы сгиба, так как проверяет наличие
из ^ http: или ^ https: чтобы определить, что он должен использовать в качестве основы
URL.

Если бы у нас были типы регулярных выражений, я мог бы определить BaseUrl как тип BaseUrl = / ^ https?: /,
и это в идеале должно правильно проверять строки, которые не являются методом HTTP или
кодирование ответов, а также не поглощение их строковым типом.

-
Вы получаете это, потому что подписаны на эту беседу.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS443DFMVREX1XWWWK3TUL52HS443DFMVREXW1
или отказаться от подписки
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

У меня возникла мысль о варианте использования, чтобы определить типы параметров функции.

В основном у меня есть четко определенный формат регулярного выражения строки, представляющей идентификатор. Я мог бы использовать декораторы, но расширенный строковый тип позволил бы мне использовать тип для представления идентификатора, переданного функции.

Повторюсь, нам нужны примеры кода JavaScript, который вы хотите написать типизированным способом - иначе мы сможем только догадываться, что вы пытаетесь смоделировать (и есть ли уже способ смоделировать это).

@DanielRosenwasser Ниже приведен пример кода, который мы хотели бы принудительно ввести. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + JU6 + вах ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt, похоже, вам нужен номинальный тип, а не тип RegExp? Вы не ожидаете, что вызывающие абоненты будут появляться со случайными проверками на сайте, например:

// OK
someFunc('a9e019b5-f527-4cf8-9105-21d780e2619b');
// Also OK, but probably really bad
someFunc('a9e019b5-f527-4cf8-9106-21d780e2619b');
// Error
someFunc('bfe91246-8371-b3fa-3m83-82032713adef');

IOW тот факт, что вы можете описать UUID с помощью регулярного выражения, является артефактом формата самой строки, тогда как то, что вы пытаетесь выразить, - это то, что UUID - это особый тип типа, поддерживающий формат которого оказывается строкой .

Таким образом, комбинация Assertion Functions в 3.7 и nominal Feature может сделать это (?)

nominal UUID = string

function someFunc(uuid: any): asserts uuid is UUID {
  if (!UUID_REGEX.test(uuid)) {
    throw new AssertionError("Not UUID!")
  }
}

class User {
  private static readonly mainUser: UUID = someFunc('a9e019b5-f527-4cf8-9105-21d780e2619b')
  // private static readonly mainUser: UUID = someFunc(123) // assertion fails
  // private static readonly mainUser: UUID = someFunc('not-a-uuid') // assertion fails
  constructor(
    public id: UUID,
    public brand: string,
    public serial: number,
    public createdBy: UUID = User.mainUser) {

  }
}

Это тоже не удастся?

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

Подумав немного, я вижу проблему с предложенным мной решением 🤔
asserts вызывает ошибку только во время выполнения -> 👎
Regex-Validation может вызвать ошибку времени компиляции -> 👍
В противном случае это предложение не имеет смысла.

Редактировать:
Другая проблема: someFunc(uuid: any): asserts uuid is UUID не возвращает UUID, он выдает или возвращает is UUID -> true .
Поэтому я не могу использовать эту функцию для присвоения UUID таким образом mainUser

@RyanCavanaugh Мы хотим, чтобы они были правильно напечатаны для Mithril:

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

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

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

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

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

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

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

Мы хотим статически отклонить это:

// selector must be non-empty
m("")

// incomplete class
m("div.")

// incomplete ID
m("div#")

// incomplete attribute
m("div[attr=")

// not special and doesn't start /[a-z]/i
m("@foo")

В идеале, мы бы также хотели статически отклонить их, но это не такой высокий приоритет, и мы можем выжить без них:

// event handers must be functions
m("div[onclick='return false']")

// `select.selectedIndex` is a number
m("select[selectedIndex='not a number']")

// `input.form` is read-only
m("input[type=text][form='anything']")

// `input.spellcheck` is a boolean, this evaluates to a string
// (This is a common mistake, actually.)
m("input[type=text][spellcheck=false]")

// invalid tag name for non-custom element
m("sv")

Для этого потребуется гораздо более сложное определение типа, в котором нам понадобится сообщение об ошибке пользовательской проверки типа, чтобы помочь пользователям выяснить, почему не удалось выполнить проверку типа.

Аналогичные проблемы вызывают и другие библиотеки гиперкриптов и фреймворки на основе гиперкриптов, такие как react-hyperscript.

Надеюсь это поможет!

@isiahmeadows - лучший способ использовать некоторую форму конструктора строк селектора, который будет возвращать фирменную строку с правильным вводом. Нравиться:

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

@ anion155 Есть и другие способы попасть туда, но речь идет о типизации библиотеки, API которой был разработан ее первоначальным автором еще в 2014 году. Если бы я проектировал ее API сейчас, я бы, вероятно, использовал m("div", {...attrs}, ...children) с ничего из гиперклассического сахара (легче набирать, гораздо проще обрабатывать), но уже слишком поздно что-то делать с этим.

Мне нужно ОЧЕНЬ много сказать. Однако я нетерпелив. Итак, я буду выпустить свои мысли понемногу.

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

Что касается "прецизионита" (мужик, я люблю это слово),
Не думаю, что мы должны слишком об этом беспокоиться.

Система типов уже завершена по Тьюрингу.
По сути, это означает, что мы можем быть очень точными во многих вещах.
(Например, моделирование всего SQL? Бесстыдный плагин = P)

Но вы не видите (слишком много) людей, которые делают все возможное и используют все операторы типов сумасшедшими способами, которые блокируют совместимость библиотек друг с другом. Мне нравится думать, что авторы библиотек имеют тенденцию быть достаточно уравновешенными ... Верно?

Нечасто мне нужны строковые шаблоны / строковые типы, проверенные регулярным выражением, но они определенно помогли бы повысить безопасность типов в моей кодовой базе.


Сценарий использования

Я могу вспомнить один недавний пример. (Есть еще куча, но я забывчивое существо)

При интеграции с Stripe API (платформа обработки платежей) они используют ch_ для идентификаторов, связанных с charge , re_ для идентификаторов, связанных с refund и т. Д.

Было бы неплохо закодировать их с помощью PatternOf</^ch_.+/> и PatternOf</^re_.+/> .

Таким образом, при опечатках вроде

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

Я бы получил ошибку,

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

Как бы мне ни нравились номинальные / помеченные типы, они гораздо более неэргономичны и подвержены ошибкам.
Я всегда рассматриваю номинальные / помеченные типы как последнее средство , потому что это означает, что есть что-то, что система типов TS просто не может смоделировать.

Кроме того, тегированные типы отлично подходят для фантомных типов.
Номинальные типы в принципе бесполезны.
(Хорошо, я могу быть предвзятым. Они полезны только из-за unique symbol но мне нравится думать, что я не совсем ошибаюсь.)

Шаблон "ValueObject" для проверки еще хуже, и я не буду об этом говорить.


Сравнение

Ниже я сравню следующее:

  • Типы строковых шаблонов / строковые типы, проверенные регулярным выражением
  • Номинальные типы
  • Типы структурных тегов

Мы все можем согласиться с тем, что шаблон «ValueObject» - худшее решение, и не заморачиваться с ним при сравнениях, верно?


Типы строковых шаблонов

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;
type StripeRefundId = PatternOf<typeof stripeRefundIdRegex>;

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (stripeChargeIdRegex.test(str)) {
  takesStripeChargeId(str); //OK
}
if (stripeRefundIdRegex.test(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //OK
takesStripeChargeId("re_hello"); //Error

Посмотри на это.

  • Идеально подходит для строковых литералов.
  • Неплохо для string нелитералов.

Номинальные типы ...

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = make_unique_type string;
type StripeRefundId = make_unique_type string;

function isStripeChargeId (str : string) : str is StripeChargeId {
  return stripeChargeIdRegex.test(str);
}
function isStripeRefundId (str : string) : str is StripeRefundId {
  return stripeRefundIdRegex.test(str);
}

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (isStripeChargeId(str)) {
  takesStripeChargeId(str); //OK
}
if (isStripeRefundId(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //Error? Ughhhh
takesStripeChargeId("re_hello"); //Error

takesStripeChargeId("ch_hello" as StripeChargeId); //OK, BUT UNSAFE
takesStripeChargeId("re_hello" as StripeChargeId); //OK, BUT WAIT! I MESSED UP

const iKnowThisIsValid = "ch_hello";
if (isStripeChargeId(iKnowThisIsValid)) {
  takesStripeChargeId(iKnowThisIsValid); //OK
} else {
  throw new Error(`Wat? This should be valid`);
}

function assertsStripeChargeId (str : string) : asserts str is StripeChargeId {
  if (!isStripeChargeId(str)) {
    throw new Error(`Expected StripeChargeId`);
  }
}
assertsStripeChargeId(iKnowThisIsValid);
takesStripeChargeId(iKnowThisIsValid); //OK

function makeStripeChargeIdOrError (str : string) : StripeChargeId {
  assertsStripeChargeId(str);
  return str;
}
takesStripeChargeId(makeStripeChargeIdOrError("ch_hello")); //OK
takesStripeChargeId(makeStripeChargeIdOrError("re_hello")); //OK, compiles, throws during run-time... Not good

Посмотри на это.

  • УЖАСНО для строковых литералов.
  • После преодоления барьера строкового литерала все не так уж плохо ... Верно?

Но основным вариантом использования этого предложения являются строковые литералы.
Итак, это ужасная альтернатива.


Типы структурных тегов ...

Структурные типы тегов мало чем отличаются от номинальных типов ...

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = string & tag { stripeChargeId : void };
type StripeRefundId = string & tag { stripeRefundId : void };

function isStripeChargeId (str : string) : str is StripeChargeId {
  return stripeChargeIdRegex.test(str);
}
function isStripeRefundId (str : string) : str is StripeRefundId {
  return stripeRefundIdRegex.test(str);
}

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (isStripeChargeId(str)) {
  takesStripeChargeId(str); //OK
}
if (isStripeRefundId(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //Error? Ughhhh
takesStripeChargeId("re_hello"); //Error

takesStripeChargeId("ch_hello" as StripeChargeId); //OK, BUT UNSAFE
takesStripeChargeId("re_hello" as StripeChargeId); //OK, BUT WAIT! I MESSED UP

const iKnowThisIsValid = "ch_hello";
if (isStripeChargeId(iKnowThisIsValid)) {
  takesStripeChargeId(iKnowThisIsValid); //OK
} else {
  throw new Error(`Wat? This should be valid`);
}

function assertsStripeChargeId (str : string) : asserts str is StripeChargeId {
  if (!isStripeChargeId(str)) {
    throw new Error(`Expected StripeChargeId`);
  }
}
assertsStripeChargeId(iKnowThisIsValid);
takesStripeChargeId(iKnowThisIsValid); //OK

function makeStripeChargeIdOrError (str : string) : StripeChargeId {
  assertsStripeChargeId(str);
  return str;
}
takesStripeChargeId(makeStripeChargeIdOrError("ch_hello")); //OK
takesStripeChargeId(makeStripeChargeIdOrError("re_hello")); //OK, compiles, throws during run-time... Not good

Посмотри на это.

  • УЖАСНО для строковых литералов.
  • После преодоления барьера строкового литерала все не так уж плохо ... Верно?

Но основным вариантом использования этого предложения являются строковые литералы.
Итак, это ужасная альтернатива.

Кроме того, этот пример структурного типа тега является буквальным (ха, каламбур) копипастом примера номинального типа .

Единственная разница в том, как объявлены типы StripeChargeId и StripeRefundId .

Хотя код в основном тот же, структурные типы лучше номинальных. (Клянусь, я проясню это в следующем посте).


Заключение

Это просто вывод к этому комментарию! Это не завершение моих общих мыслей!

Типы строковых шаблонов / строковые типы, проверенные регулярным выражением , более эргономичны, чем номинальные / структурные типы тегов. Надеюсь, мои простые примеры не были слишком надуманными , что достаточно продемонстрировали.


Заключение (Extra)

Насколько это возможно, способы взятия подмножества примитивного типа всегда должны быть предпочтительнее номинальных / структурных типов тегов / объектов-значений.

Примеры взятия подмножества примитивных типов,

  • string литералы
  • number литералы (исключая NaN, Infinity, -Infinity )
  • boolean литералы
  • bigint литералы
  • Даже unique symbol просто берет подмножество symbol

Из приведенных выше примеров только boolean является "достаточно конечным". У него всего два значения.
Разработчики довольны наличием литералов true и false потому что больше не о чем просить.


Тип number конечен, но у него так много значений, что мы могли бы считать его бесконечным.
Также есть дыры в том, какие литералы мы можем указать.

Вот почему тип номера диапазона и проблемы NaN, Infinity, -Infinity так популярны и продолжают появляться. Недостаточно возможности указать небольшой конечный набор значений из бесконечного набора.

Определение диапазона - одна из наиболее распространенных / естественных идей, которые приходят кому-то, когда им нужно указать большое конечное / бесконечное подмножество бесконечного множества.


Тип bigint в основном бесконечен, ограничен только памятью.

Это также способствует популярности проблемы типа номера диапазона.


Тип string в основном бесконечен, ограничен только памятью.

И именно поэтому проблема строкового типа / проверенного регулярного выражения строкового типа так популярна.

Определение регулярного выражения - одна из самых распространенных / естественных идей, которые приходят кому-то, когда им нужно указать большое конечное / бесконечное подмножество бесконечного множества.


Тип symbol ... Он тоже бесконечен. А также без ограничений, в значительной степени.

Но элементы типа symbol практически никак не связаны друг с другом. И поэтому никто не ставил вопрос: «Могу ли я найти способ указать большое конечное / бесконечное подмножество symbol ?».

Для большинства людей этот вопрос даже не имеет смысла. Нет разумного способа сделать это (правда?)


Однако просто возможность объявлять подмножества примитивов не очень полезна. Нам также нужно,

  • Литералы правильного типа должны быть назначены без дальнейшей работы

К счастью, TS достаточно вменяемый, чтобы позволить это.

Представьте, что вы не можете передать false (arg : false) => void !

  • Встроенные способы сужения

    На данный момент для этих литералов у нас есть == & === как встроенные способы сужения.

    Представьте, что вам нужно написать новую защиту типа для каждого литерала!

Проблема с типами номинальных / структурных тегов / объектов-значений заключается в том, что они в основном не соответствуют двум указанным выше критериям. Они превращают примитивные типы в неуклюжие типы, которые не совсем объектные, но в любом случае с ними нужно обращаться как с объектными типами.

Эргономика

Хорошо, вот более подробная информация о строковом шаблоне, номинальном и структурном типах тегов.

Эти аргументы также применимы к https://github.com/microsoft/TypeScript/issues/15480 .


Межбиблиотечная совместимость

Номинальные типы хуже всего подходят
Это все равно, что использовать unique symbol в двух библиотеках и пытаться заставить их взаимодействовать.
Это просто невозможно.
Вам нужно использовать стандартную защиту типа или оператор доверия ( as ).

Вам также понадобится больше шаблонов для защиты от утверждений.

Если тип не требует кросс-библиотечной совместимости, то можно использовать номинальные типы ...
Даже если он очень неэргономичный (см. Пример выше).


Для структурных типов, если в библиотеке A есть,

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

А в библиотеке B есть,

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

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

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

Тогда вам понадобится защитник шаблонного типа или оператор доверия ( as ).

Вам также понадобится больше шаблонов для защиты от утверждений.

Если тип не требует кросс-библиотечной совместимости, то можно использовать структурные типы ...
Даже если он очень неэргономичный (см. Пример выше).


Для типов строковых шаблонов, если в библиотеке A есть,

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

А в библиотеке B есть,

//Extra dollar sign at the end
type stripeChargeIdRegex = /^ch_.+$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

//Or,
type stripeChargeIdRegex =/^ch_[a-zA-Z0-9]$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

//Or,
type stripeChargeIdRegex =/^ch_[A-Za-z0-9]$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

Предположим, что обе библиотеки всегда создают строки для StripeChargeId , которые удовлетворяют требованиям обеих библиотек. Библиотека A просто «ленива» с проверкой. А библиотека B «строже» с проверкой.

Тогда это немного раздражает. Но не так уж и плохо.
Потому что вы можете просто использовать libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) в качестве защиты типа. Нет необходимости использовать оператор доверия ( as ).

Тем не менее, вам все равно понадобится шаблон для защиты от утверждений.

Если тип не требует кросс-библиотечной совместимости, то использование строковых шаблонов идеально, а также очень эргономично.


Если вам нужна кросс-библиотечная совместимость, типы строковых шаблонов все же лучше, чем типы структурных тегов! Выслушай меня.

Если моделируемый домен хорошо изучен, то весьма вероятно, что несколько авторов изолированных библиотек в конечном итоге напишут одно и то же регулярное выражение. Со структурными типами тегов все они могут просто записывать в теги любые свойства и типы.

Если существует стандарт, определяющий строковые форматы для всего, что моделируется, то в основном гарантируется, что все авторы библиотеки напишут одно и то же регулярное выражение! Если они пишут другое регулярное выражение, они на самом деле не следуют стандарту. Вы хотите использовать их библиотеку? Со структурными типами тегов они все еще могли писать что угодно. (Если только кто-то не запустит стандарт типа структурного тега, который всем будет небезразличен? Lol)


Межверсионная совместимость

Как правило, номинальные типы хуже всех подходят
О, вы наткнули свою библиотеку патчем или минорной версией?
Тип декаларации остался прежним?
Код все тот же?
Неа. Они разные.

image


Типы структурных тегов по-прежнему присваиваются в разных версиях (даже в основных версиях), если тип тегов структурно один и тот же.


Типы строковых шаблонов по-прежнему присваиваются в разных версиях (даже в основных версиях), если регулярное выражение остается тем же.

Или мы могли бы просто запустить алгоритм PSPACE-complete, чтобы определить, совпадают ли регулярные выражения? Мы также можем определить, какие подклассы регулярных выражений являются наиболее распространенными, и запустить оптимизированные алгоритмы эквивалентности для них ... Но это похоже на большие усилия.

Было бы здорово иметь проверки подтипа регулярных выражений, и они определенно сделали бы использование строковых шаблонов более эргономичным. Точно так же, как проверка подтипа диапазона принесет пользу предложению типа диапазона номеров.

[Редактировать]
В этом комментарии
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Кто-то связан с,
https://bora.uib.no/handle/1956/3956

Под названием «Проблема включения регулярных выражений».
[/Редактировать]


Boilerplate

ЗАДАЧА (но мы видим, что типы строковых шаблонов имеют наименьшее количество шаблонов)

Буквальный призыв

ЗАДАЧА (Но мы видим, что строковые шаблоны лучше всего поддерживают буквальный вызов)

Не-буквальный вызов

TODO (но мы видим, что типы строкового шаблона лучше всего поддерживают нелитеральный вызов)

Подробнее о https://github.com/microsoft/TypeScript/issues/6579#issuecomment -542405537

TypeScript не может ошибаться при создании экземпляров пересечений, поэтому это не будет частью окончательного дизайна.

Я не знаю, почему люди хотели запретить перекрестки, но вы абсолютно правы в том, что запретить это не имеет смысла.


таким образом нарушая все небуквенные обращения?

Что ж, не всякий небуквальный призыв.

declare function foo (arg : PatternOf</a+/>) : void;
function bar (arg : PatternOf</a+/>) : void {
  //non-literal and does not break.
  foo(arg);
}
bar("aa"); //OK
bar("bb"); //Error
bar("" as string); //Error, I know this is what you meant by non-literal invocation

function baz (arg : "car"|"bar"|"tar") : void {
  bar(arg); //OK
}

Нарушение небуквального вызова, когда он не может доказать, что он соответствует регулярному выражению, не обязательно плохо. Это просто вопрос безопасности типов.

Это все равно что сказать, что строковые литералы плохи, потому что теперь нелитеральные вызовы терпят неудачу.
Типы строковых шаблонов / строковые типы, проверенные регулярным выражением, просто позволяют вам определять объединения бесконечного числа строковых литералов.


любое небуквальное использование потребует повторного тестирования или утверждения:

Я не вижу в этом проблемы.
То же самое и с номинальными / помеченными типами прямо сейчас.
Или попытка передать string функции, ожидающей строковые литералы.
Или пытаетесь передать более широкий шрифт более узкому.

В этом конкретном случае вы показали, что const ZipCode = /^\d\d\d\d\d$/; и ZipCode.test(s) могут действовать как защита типа. Это, безусловно, поможет с эргономикой.


  • У решаемой проблемы нет лучшей альтернативы (включая правдоподобные альтернативы, которых еще нет на языке).

Надеюсь, я показал, что номинальные / структурные типы тегов - не лучшая альтернатива. Они на самом деле очень плохие.

  • Проблема возникает со значимой частотой в реальных кодовых базах.

Эээ ... Позвольте мне вернуться к вам по этому поводу ...

  • Предлагаемое решение хорошо решает эту проблему.

Предлагаемый тип строкового шаблона кажется довольно хорошим.


ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

По вашему мнению, номинальные / помеченные типы достаточно хороши для небуквального использования.
Итак, любой приведенный вариант использования, который показывает нелитальное использование, недостаточно хорош, потому что номинальные / помеченные типы охватывают его.

Однако мы видели, что даже при небуквальном использовании

  • Номинальные / структурные типы тегов страдают от проблем совместимости между библиотеками и версиями
  • Количество шаблонов для номинальных / структурных типов тегов значительно больше, чем для шаблонных типов строковых шаблонов.

Кроме того, кажется, что приведенные буквальные варианты использования вас не удовлетворили, потому что они пытаются делать глупые вещи, такие как проверка электронной почты, или используют регулярные выражения, которые недостаточно точны.


Написание тестов - здесь есть смысл жестко запрограммированные входные данные, хотя это почти противоположность, потому что ваш тестовый код, вероятно, должен предоставлять много недопустимых входных данных.

Поднятый хороший пример использования - это написание тестов во время выполнения . И вы правы, что они должны бросать в него много недопустимых входных данных для тестов во время выполнения.

Но это не причина не поддерживать типы строковых шаблонов. Может случиться так, что они захотят протестировать допустимые входные данные в определенном файле и случайно предоставят недопустимые входные данные.

Но поскольку они должны использовать охранник типа или оператор доверия ( as ) или объект значения, теперь они получат ошибку времени выполнения, вместо того, чтобы знать, что тест завершится сбоем раньше времени. .

Использование оператора «доверься мне» ( as ) для тестов во время выполнения должно быть зарезервировано только для проверки недопустимых входных данных. Когда вы хотите протестировать действительные входные данные, более ясно, что не нужны хаки для присвоения литералов номинальному / структурному типу тега.

Если они когда-либо изменят регулярное выражение в будущем, было бы хорошо, если бы их тесты теперь даже не запускались из-за проблем с назначением. Если они просто используют as везде в своих тестах, они не узнают, пока не запустят тесты.

И если автор библиотеки просто везде использует as при подкармливании собственной библиотеки ... А как насчет последующих потребителей? Не возникнет ли у них искушение использовать везде as и не столкнутся ли они с проблемами во время выполнения при обновлении до новой версии?

С типами строкового шаблона меньше причин использовать везде as и как автор библиотеки, так и последующие потребители будут легче узнавать о нарушении изменений.

(Немного затянувшееся, но я надеюсь, что некоторые из моих пунктов были достигнуты).


Кроме того, я пишу много тестов во время компиляции (и я знаю, что команда TS тоже делает то же самое).

Было бы неплохо, если бы я мог проверить, что некий литерал string не сработает / пройдет проверку регулярного выражения в моих тестах времени компиляции. На данный момент у меня нет тестов для этих вещей во время компиляции, и вместо этого мне нужно использовать тест во время выполнения.

И если он не проходит / проходит мои тесты во время компиляции, тогда я буду уверен, что нижестоящие потребители могут использовать эти строковые литералы (или аналогичные) и ожидать от них правильного поведения.


Похоже, это быстро ставит нас на путь к ситуации с Вавилонской башней ...

На самом деле это еще более верно в отношении использования номинальных / структурных типов тегов. Как показали приведенные выше примеры, они ужасно подходят для совместимости между библиотеками / версиями ...

Однако типы регулярных выражений / строковых шаблонов имеют приличный шанс не столкнуться с этой проблемой (надеюсь, благодаря стандартизации и разумным авторам библиотеки).


РЕДАКТИРОВАТЬ

Распространенной вещью в потоке является то, что эти регулярные выражения могут помочь проверить тестовый код, потому что даже если в производственных сценариях код будет работать со строками, предоставленными во время выполнения, а не с жестко закодированными литералами, вам все равно понадобится некоторая проверка того, что ваши тестовые строки были " верный". Это может показаться аргументом для номинальных / помеченных / брендированных строк, хотя, поскольку вы в любом случае пишете функцию проверки, а преимущество тестов состоит в том, что вы знаете, что они выполняются исчерпывающе (таким образом, любые ошибки во входных данных теста будут быть отмеченным в начале цикла разработки).

Ах ... Я должен был сначала все прочитать, прежде чем писать это ...

В любом случае, у меня есть несколько примеров, в которых типы строковых шаблонов могут быть полезны.


Библиотека декларации маршрутов HTTP

С помощью этой библиотеки вы можете создавать объекты объявления маршрутов HTTP. Это объявление используется как клиентом, так и сервером.

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

Это ограничения для .append() ,

  • Только строковые литералы (на данный момент это невозможно, но если вы используете нелитералы, построитель объявления маршрута становится мусором)
  • Должен начинаться с косой черты в начале ( / )
  • Не должно заканчиваться косой чертой в конце ( / )
  • Не должно содержать символа conlon ( : ); это зарезервировано для параметров
  • Не должно содержать подряд двух или более косых черт ( // )

Прямо сейчас у меня есть только проверки времени выполнения для тех, которые вызывают ошибки. Я бы хотел, чтобы нижестоящие потребители следовали этим ограничениям без необходимости читать комментарий Github README или JSDoc. Просто напишите путь и увидите красные волнистые линии.


Другие вещи

У меня также есть регулярные выражения для шестнадцатеричных строк, буквенно-цифровых строк.

У меня тоже есть это,

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

Я вижу это,

Целое - неверно отклоняет "3e5"

У меня также есть это, которое не является целочисленным регулярным выражением, но использует floatingPointRegex ,

function parseFloatingPointString (str : string) {
    const m = floatingPointRegex.exec(str);
    if (m == undefined) {
        return undefined;
    }
    const rawCoefficientSign : string|undefined = m[1];
    const rawCoefficientValue : string = m[2];
    const rawExponentSign : string|undefined = m[4];
    const rawExponentValue : string|undefined = m[5];

    const decimalPlaceIndex = rawCoefficientValue.indexOf(".");
    const fractionalLength = (decimalPlaceIndex < 0) ?
        0 :
        rawCoefficientValue.length - decimalPlaceIndex - 1;

    const exponentValue = (rawExponentValue == undefined) ?
        0 :
        parseInt(rawExponentValue) * ((rawExponentSign === "-") ? -1 : 1);

    const normalizedFractionalLength = (fractionalLength - exponentValue);
    const isInteger = (normalizedFractionalLength <= 0) ?
        true :
        /^0+$/.test(rawCoefficientValue.substring(
            rawCoefficientValue.length-normalizedFractionalLength,
            rawCoefficientValue.length
        ));
    const isNeg = (rawCoefficientSign === "-");

    return {
        isInteger,
        isNeg,
    };
}

У меня также есть этот комментарий,

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

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

Конструктор RegExp

Как ни странно, конструктор RegExp выиграет от строковых типов, проверенных регулярным выражением!

Прямо сейчас это так,

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

Однако мы могли бы,

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

TL; DR (Пожалуйста, прочтите это, я приложил много усилий: cry:)

  • Типы строковых шаблонов более эргономичны, чем типы номинальных / структурных тегов

    • Меньше шаблонов

  • Типы строковых шаблонов с меньшей вероятностью, чем типы номинальных / структурных тегов, станут ситуацией Вавилонской башни

    • Особенно с проверками подтипа регулярных выражений

  • Типы строковых шаблонов - наиболее естественный способ определения больших конечных / бесконечных подмножеств типа string

    • Введение этой функции может даже побудить людей более внимательно задуматься о допустимых строковых форматах для своих библиотек!

  • Типы строковых шаблонов обеспечивают более надежную безопасность во время компиляции для некоторых библиотек (позвольте мне вернуться к вам по поводу распространенности ... убегает )

    • Конструктор RegExp, шестнадцатеричные / буквенно-цифровые строки, объявления путей маршрута, строковые идентификаторы для баз данных и т. Д.


Почему у вас такие плохие регулярные выражения?

В ряде случаев использования, предложенных другими, хотелось ввести типы строковых шаблонов, подходящие для существующих библиотек; и, похоже, это не убеждает команду TS.

Часто мне кажется, что эти существующие библиотеки даже не слишком часто используют регулярные выражения для проверки вводимых данных. Или они используют регулярное выражение для выполнения простой проверки. Затем они используют более сложный синтаксический анализатор для выполнения фактической проверки.

Но это действительно допустимый вариант использования для типов строковых шаблонов!


Типы строковых шаблонов для проверки надмножеств допустимых строковых значений

Конечно, строка, которая начинается с / , не заканчивается на / , не содержит последовательных / и не содержит : , передается знак " Регулярное выражение пути HTTP ". Но это просто означает, что набор значений, которые передают это регулярное выражение, является надмножеством допустимых путей HTTP.

Далее у нас есть фактический парсер пути URL, который проверяет, что ? не используется, # не используется, некоторые символы экранированы и т. Д.

Но с помощью этого простого типа строкового шаблона мы уже устранили большой класс общих проблем, с которыми может столкнуться пользователь библиотеки! И мы также устранили это во время компиляции!

Нечасто пользователь будет использовать ? в своих HTTP-путях, потому что большинство из них достаточно опытны, чтобы знать, что ? - это начало строки запроса.


Я только что понял, что вы уже знаете об этом варианте использования.

Эта ветка подразумевает множество вариантов использования; конкретные примеры были реже. К сожалению, многие из этих примеров кажутся неполными - в них используется регулярное выражение, которое отклоняет допустимые входные данные.

Итак, конечно, многие из предложенных регулярных выражений не «завершены».
Но если они не отклоняют действительный ввод, все должно быть в порядке, верно?

Ничего страшного, если они разрешают недопустимый ввод, верно?
Поскольку у нас может быть «настоящий» синтаксический анализатор во время выполнения, он будет обрабатывать полную проверку.
А проверка во время компиляции может устранить множество общих проблем для последующих пользователей, повышая производительность.

Те примеры, которые отклоняют действительный ввод, должны быть достаточно легкими для изменения, чтобы они не отклоняли действительный ввод, но допускали недопустимый ввод.


Типы строковых шаблонов и пересечения

В любом случае, типы пересечений для типов строковых шаблонов были бы очень полезны!

Мой пример .append() можно было бы записать как,

append (str : (
  //Must start with forward slash
  & PatternOf</^\//>
  //Must not end with forward slash
  & PatternOf</[^/]$/>
  //Must not have consecutive forward slashes anywhere
  & not PatternOf</\/\//>
  //Must not contain colon
  & PatternOf</^[^:]+$/>
)) : SomeReturnType;

not PatternOf</\/\//> также может быть,
PatternOf</^((([/])(?!\3))|[^/])+$/> но это намного сложнее

Спасибо, @AnyhowStep , за обширные демонстрации. Я хотел покритиковать вас за то, что вы заставили меня так много читать, но это оказалось очень полезно!

Мне часто сложно набрать свой внутренний API, полный строковых параметров, и я неизбежно получаю множество условных выражений, которые возникают во время выполнения. Мои потребители неизбежно должны дублировать эти проверки шаблонов, поскольку им не нужно исключение, им нужен особый способ обработки сбоя.

// Today
function createServer(id: string, comment: string) {
  if (id.match(/^[a-z]+-[0-9]+$/)) throw new Error("Server id does not match the format");
  // work
}

// Nicer
function createServer(id: PatternOf</^[a-z]+-[0-9]+$/>, comment: string) {
  // work immediately
}

В мире строк и шаблонов общий string почти такой же, как unknown , устраняя большую часть безопасности типов в пользу проверок во время выполнения и доставляя неудобства моим разработчикам-потребителям.

Для некоторых из упомянутых случаев использования потребуется лишь небольшое подмножество Regex, например сопоставление префиксов.

Потенциально это можно сделать с помощью более общих функций языка TS, таких как Variadic Kinds # 5453 и вывода типов при распространении строковых литералов.

Будущие предположения:

const x: ['a', 'b', 'c'] = [...'abc'] as const;

type T = [...'def']; // ['d', 'e', 'f'];
type Guard<T extends string> =
  [...T] extends [...'https://', ...any[]] ? Promise<any> : never;

declare function secureGET<
  T extends string
>(url: T): Guard<T>;

const x = secureGET('https://a.com');
x.then(...) // okay

const z = secureGET('http://z.com');
z.then(...); // error
type NaturalNumberString<T extends string> =
  [...T] extends ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')[] ? T : never;

Для некоторых из упомянутых случаев использования потребуется лишь небольшое подмножество Regex, например сопоставление префиксов.

Я все еще поддерживаю свое предложение , которое предлагает это + несколько других вещей, в основном очень небольшой набор языков без звезд, которые вы все еще можете достаточно эффективно проверять на подмножество и равенство. И до сих пор я не встречал никаких других предложений, направленных на решение аспекта производительности произвольных регулярных выражений, что является самой большой проблемой для команды TS.

Проблема с языками без звездочек в том, что, как следует из названия, нельзя использовать звездочки, что затрудняет проверку таких вещей, как URL-адреса. Кроме того, большинство людей, вероятно, захотят звездочки и просто используют произвольное количество повторяющихся последовательностей для их имитации, что затруднит проверку подмножеств.

И производительность большинства обычных регулярных выражений, представляемых DFA, не так уж плоха, и их можно проверить на наличие подмножеств / надмножеств.

Тем не менее, вы все равно можете получить * .

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

@TijmenW Прочтите мое предложение немного внимательнее - там есть некоторые скрытые starof ('a' | 'b' | ...) для отдельных символов, и вы можете использовать string как эквивалент starof UnionOfAllCodePoints (фактически делая его больше не примитивом в теории).

Кроме того, проверка того, соответствует ли регулярный язык подмножеству того, что соответствует другому регулярному языку, является NP-полной и эквивалентна общей проблеме изоморфизма подграфов . Вот почему вы не можете просто использовать стандартные регулярные языки, и почему я попытался максимально ограничить starof , чтобы попытаться снизить теоретическую вычислительную сложность.

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

Воспринимайте это с долей скептицизма, поскольку это совершенно новая библиотека, но любая библиотека, такая как https://github.com/ostrowr/ts-json-validator, будет намного полезнее с чем-то вроде типа регулярного выражения.

Целью библиотеки является создание пар типа TypeScript / схемы JSON <T, s> таких что

  1. Любой тип, который может проверить s может быть назначен T
  2. Как можно меньше типов, которые можно присвоить T не пройдут проверку при запуске с s .

Тип регулярного выражения улучшит строгость (2), позволяя проверяемому типу быть более строгим по крайней мере в отношении следующих ключевых слов:

  • format
  • patternProperties
  • propertyNames

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

Все библиотеки интерфейса Excel могут использовать проверку типа как A1 или A5:B7 .

Ключи свойств / Индексаторы строк регулярных выражений

Некоторые библиотеки обрабатывают объекты в соответствии с именами свойств. Например, в React мы хотим применить типы к любой опоре, имя которой начинается с aria- :

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

По сути, это ортогональная концепция (мы могли бы добавлять типы Regex без добавления ключей свойств Regex, и наоборот).

Я знаю, что это немного ортогонально всему, что здесь происходит, но Уэсли подумал, что вы могли бы использовать наш вклад. Это продолжает появляться в Fabric по нескольким причинам. Как библиотека компонентов, мы хотим иметь возможность повысить уровень интерфейса свойств компонента, который точно отражает интерфейс компонента React, разрешенный TypeScript, включая атрибуты data- и aria- . Без него мы не сможем предоставить нашим потребителям точные интерфейсы для использования с этими атрибутами. Это становится более серьезной проблемой в следующей версии Fabric, в которой мы рассматриваем подключаемые реализации, такие как слоты, и должны определять и разрешать эти атрибуты на произвольных интерфейсах.

Если мы можем чем-то помочь, дайте мне знать! 😄

ТС Детская площадка :

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

ЗАДАЧА: Пожалуйста, помогите нам, определив реальные библиотечные функции, которым могут быть полезны типы RegExp, и фактическое выражение, которое вы бы использовали.

Cron вакансии. (очень удивлен, что об этом не упомянули)

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

Просто добавляю сюда свои два цента - я работаю над проектом React, в котором мы хотели бы проверить опору, которая будет использоваться в качестве атрибута HTML id . Это означает, что он должен соответствовать следующим правилам, иначе произойдет непредвиденное поведение:

  1. Иметь хотя бы одного персонажа
  2. Нет пробелов

Другими словами:

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

Другой пример: идентификаторы-типа-святилища со строками, ожидаемыми в формате '<namespace>/<name>[@<version>]'

Пример использования: API DOM с строковой типизацией, например Navigator.registerProtocolHandler() .

Цитата MDN:

По соображениям безопасности registerProtocolHandler() ограничивает, какие схемы могут быть зарегистрированы.

Индивидуальная схема может быть зарегистрирована, если:

  • Имя настраиваемой схемы начинается с web+
  • Имя настраиваемой схемы включает как минимум 1 букву после префикса web+
  • В названии пользовательской схемы используются только строчные буквы ASCII.

Другими словами, Navigator.registerProtocolHandler() ожидает либо хорошо известного string либо пользовательского string но только если он соответствует определенной схеме.

Настраиваемые свойства CSS для CSSType - это еще один вариант использования закрытых типов для всех свойств, кроме тех, с префиксом -- .

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

Связанные https://github.com/frenic/csstype/issues/63

Может кто-нибудь сказать мне, совпадает ли это с типами уточнения? https://github.com/microsoft/TypeScript/issues/7599

@ gautam1168 Теоретически это всего лишь подмножество, в котором конкретно

Для некоторых из упомянутых случаев использования потребуется лишь небольшое подмножество Regex, например сопоставление префиксов.

Я все еще поддерживаю свое предложение , которое предлагает это + несколько других вещей, в основном очень небольшой набор языков без звезд, которые вы все еще можете достаточно эффективно проверять на подмножество и равенство. И до сих пор я не встречал никаких других предложений, направленных на решение аспекта производительности произвольных регулярных выражений, что является самой большой проблемой для команды TS.

В этом комментарии
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Кто-то связан с,
https://bora.uib.no/handle/1956/3956

Под названием «Проблема включения регулярных выражений».


Тем не мение,

  • Если правое выражение однозначно 1, алгоритм дает правильный ответ.
  • В противном случае он может дать правильный ответ или нет.

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

(Конечно, регулярные выражения JS не являются регулярными)

@AnyhowStep Это может сработать - я бы просто снял ограничение starof и соответственно изменил это ограничение. Я хотел бы лучше охарактеризовать ограничение, поскольку математика немного абстрактна и неясно, как она будет конкретно применяться на практике (не все, кто будет использовать эти типы, хорошо разбираются в формальных языках).

Кроме того, отдельно я бы очень хотел получить лучшую альтернативу starof в качестве оператора для моделирования подобных вещей.

Мне любопытно: можно ли разрешить включение / включение регулярных выражений? Согласно Википедии , это разрешимо. Однако учитывает ли это также регулярные выражения в JS? Я думаю, что у них больше возможностей, чем у стандартных RE (например, обратные ссылки). Если это разрешимо, возможно ли это с вычислительной точки зрения?
Это повлияет на эту функцию (сужение):

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

@nikeee Decidable недостаточно, чтобы это было реалистично. Даже квадратичное время в этом масштабе обычно слишком медленное. Не TS, но у меня есть некоторая предыстория по аналогичным вопросам.

Несмотря на обратные ссылки, я подозреваю, что это все еще разрешимо, но, вероятно, экспоненциально, если не хуже. Однако это всего лишь обоснованное предположение.

Спасибо за разъяснение!

Даже квадратичное время в этом масштабе обычно слишком медленное.

Вот почему я также спросил, возможно ли это с вычислительной точки зрения, так что я думаю, что это не так.

Если то же самое относится к равенству, не означает ли это, что почти все свойства этой функции недопустимы? Поправьте меня, если я ошибаюсь, но вроде осталось только членство. Не думаю, что одно это было бы полезно.

@nikeee Следует буквально по каждому свойству каждого типа, с которым он сравнивается . А для типов со свойствами регулярного выражения вам действительно нужно вычислить, соответствует ли регулярное выражение подмножеству того, что соответствует другому регулярному выражению, что само по себе является довольно сложным зверьком.

Это не невозможно, просто сложно , и вы должны быть ограничительными, если хотите, чтобы это было осуществимо. (Во-первых, регулярные выражения JS не будут работать - они не только недостаточно расширяемы, но и слишком гибки.)

Изменить: я хочу повторить это: я не в команде TS, просто чтобы уточнить. У меня просто приличный опыт в разработке алгоритмов CS.

Хм, так что, возможно, вы сможете поддерживать только «ограниченное» подмножество «обычных» RegEx. Учитывая варианты использования, регулярные выражения пока были довольно простыми… (цвета, номера телефонов и т. Д.)

Как мы могли спроектировать UX, поддерживающий только часть? Для пользователя может быть не совсем понятно, что функция X RegEx работает, а Y - нет.

ну ... не называйте это "регулярным выражением" - для начала. Может быть, просто "сопоставление с образцом" или что-то вроде этого: see_no_ evil :. Но да, наверное, задача не из легких ...

Как насчет синтаксиса без регулярных выражений, подобного этому:

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type URL = `${'http'|'https'}://${Domain}`;

const good: URL = 'https://google.com'; // ✔️
const bad: URL = 'ftp://example.com'; // ✖️ TypeError: 'ftp' is not assignable to type 'http' | 'https'

На мой взгляд, это прекрасно вписалось бы в систему типов. Вы можете добавить другой синтаксис для таких вещей, как необязательные совпадения:

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

Добавьте поддержку квантификаторов, жадного оператора, и вы получите что-то довольно надежное, я думаю, этого, вероятно, будет достаточно для большинства случаев использования, для которых разработчики могут захотеть это использовать.

Думаю, такой подход был бы более удобным для пользователя. Однако, похоже, он эквивалентен арифметическим операциям над типами.
Согласно https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 и https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109, это дизайнерское решение не делать арифметика по типам.
Если я не ошибаюсь, такой подход позволяет легко создать огромный шрифт. Рассмотреть возможность:

type TLD = 'com' | 'net' | 'org' | 'ly' | 'a' | 'b' | 'c' | 'd';
type Foo = `${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}`;
type Bar = `${Foo}${Foo}${Foo}${Foo}${Foo}`

(предполагается, что реализация будет использовать типы объединения. Она может работать с другой / более сложной реализацией)

Заявление об отказе от ответственности: я не являюсь частью команды TS и не работаю над TS. Просто мой 2с.

@rozzzly @nikeee Это более или менее суть моего предложения , за исключением нескольких меньших функций. Я основывал свой на большом подмножестве регулярных языков (концепция формального языка), а не на регулярных выражениях в смысле литералов регулярных выражений и т. Д., Поэтому он намного менее мощный, чем те, но достаточно мощный, чтобы выполнить свою работу.

Думаю, такой подход был бы более удобным для пользователя. Однако, похоже, он эквивалентен арифметическим операциям над типами.

Математика утверждает, что проверка того, является ли тип подтипом другого, с вычислительной точки зрения эквивалентна проверке того, содержится ли строка в данном формальном языке.

В частности, проверка домена на самом деле является довольно сложной задачей, если вы также проверяете действительность TLD / общедоступного суффикса . Сами общие домены в соответствии с RFC так же просты, как /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ +, состоящий не более чем из 255 символов, но даже это очень сложно набрать, если вы не используете полные регулярные грамматики, как демонстрирует приведенное выше регулярное выражение. Вы можете программно сгенерировать тип довольно просто (я оставлю это в качестве упражнения для читателя), используя только строки из @rozzzly или моего предложения, но конечный результат все еще довольно сложен.

@isiahmeadows

Это более или менее суть моего предложения , за исключением нескольких небольших функций.

В последний раз я прочитал всю эту ветку более года назад. У меня был перерыв, и я увидел уведомление, прочитал комментарий @rugk о _ "ну ... не называйте это" регулярным выражением "- для начала" _, что заставило меня задуматься ... Я не понял, что кто-то уже это сделал представил значительно более подробное предложение по существу той же _ (/ очень похожей) _ идеи.

... даже это очень сложно набрать, если вы не используете полные регулярные грамматики, как демонстрирует приведенное выше регулярное выражение. Вы можете программно сгенерировать тип довольно просто (я оставлю это в качестве упражнения для читателя), используя только строки из @rozzzly или моего предложения, но конечный результат все еще довольно сложен.

На мой взгляд, некоторые средства для ограниченного сопоставления с образцом, такие как я предложил разрешить, были бы чрезвычайно полезны для очень упрощенной и _ обязательно не строго_ строгой_ типизации. Приведенный мной пример далеко не точен и не взорвет компилятор.

Но, как отмечают вы и доходить до опасной крайности. Предполагая наиболее наивную реализацию, поддерживающую только профсоюзы. Кто-то собирается испортить всеобщий день публикацией обновления для @types/some-popular-project , содержащего:

type MixedCaseAlphaNumeric = (
    | 'a'
    | 'b'
    | 'c'
    // and so on
);

type StrWithLengthBeteen1And64<Charset extends string> = (
    | `${Charset}`
    | `${Charset}|${Charset}`
    | `${Charset}|${Charset}|${Charset}`
    // and so on
);

function updatePassword(userID: number, password: StrWithLengthBetween1And64<MixedCaseAlphaNumeric>): void {
    // ...
}

Если рассматривать это в перспективе, этот союз состоит из различных типов, которые больше, чем атомы в наблюдаемой Вселенной .

Я видел несколько ужасно длинных ошибок присваивания, но представьте себе (неусеченную) ошибку для этого ...

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

Так что да ... там есть некоторые проблемы

@rozzzly Что отличает этот тип (с точки зрения осуществимости) от TupleWithLengthBeteen1And64<Charset> ?
Компилятору не нужно расширять каждый тип до нормализованной формы, в противном случае он бы быстро взорвался на довольно нормальных типах.
Я не говорю, что я думаю, что эта проблема имеет смысл в машинописном тексте на данный момент, если даже «целое число от 3 до 1024» (подумайте о длине распределения буфера сообщений) считается выходящим за рамки.

@simonbuchan По крайней мере, должны существовать типы префиксов и суффиксов. Это само по себе требуется для многих библиотек и фреймворков DOM.

Я знаю, что это забито до смерти, и уже поступили несколько хороших предложений. Но я просто хотел добавить дополнительные вещи, которые некоторым могут показаться умеренно интересными.

Несмотря на обратные ссылки, я подозреваю, что это все еще разрешимо, но, вероятно, экспоненциально, если не хуже. Однако это всего лишь обоснованное предположение.

Обратные ссылки могут заставить регулярное выражение описывать контекстно-зависимую грамматику, надмножество контекстно-свободных грамматик. И языковое равенство для CFG неразрешимо. Так что еще хуже для CSG, которые эквивалентны автоматам с линейными ограничениями.


Предполагая, что в регулярном выражении используются только все регулярные выражения, которые могут быть преобразованы в DFA (объединение, объединение, звезда, пересечение, дополнение и т. Д.), Преобразование регулярного выражения в NFA выполняется за O (n), получая произведение двух NFAs составляет O (m * n), тогда обход результирующего графа для состояний принятия составляет O (m * n). Таким образом, проверка языкового равенства / подмножества двух регулярных регулярных выражений также является O (m * n).

Проблема в том, что алфавит здесь действительно большой. Учебники обычно ограничиваются алфавитами размера 1-5, когда говорят о DFA / NFA / регулярных выражениях. Но с регулярными выражениями JS у нас есть весь Юникод в качестве алфавита. Конечно, могут быть эффективные способы представления функций перехода с использованием разреженных массивов и других хитрых приемов и оптимизаций для проверки равенства / подмножества ...

Я уверен, что можно довольно эффективно выполнять проверку типов для регулярного назначения.

Тогда все нерегулярные присваивания могут просто требовать явных утверждений типа.

Я недавно работал над небольшим проектом конечного автомата, так что информация все еще свежа в моей памяти = x

Если я не ошибаюсь, такой подход позволяет легко создать огромный шрифт. Рассмотреть возможность:

type TLD = 'com' | 'net' | 'org' | 'ly' | 'a' | 'b' | 'c' | 'd';
type Foo = `${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}`;
type Bar = `${Foo}${Foo}${Foo}${Foo}${Foo}`

(предполагается, что реализация будет использовать типы объединения. Она может работать с другой / более сложной реализацией)

Как ни странно, это именно то, что возможно с новыми типами строковых литералов шаблона. Похоже, этого случая можно избежать, установив порог для типов объединения.

@AnyhowStep JS

Изменить: точность

Я подтвердил, что этот комментарий от @rozzzly работает с TS 4.1.0 каждую ночь!

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type Url = `${'http'|'https'}://${Domain}`;

const success: Url = 'https://example.com';
const fail: Url = 'example.com';
const domain: Domain = 'example.com';

Попробуйте это на игровой площадке и увидите, что fail имеет ошибку времени компиляции 🤩


Обновление : немного поиграв с этой функцией, она не охватит многие варианты использования. Например, это не работает для шестнадцатеричной цветовой строки.

type HexChar = '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type HexColor = `#${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}`;
let color: HexColor = '#123456';

Сегодня это не удается: «Выражение создает тип объединения, который слишком сложен для представления (2590)».

Я подтвердил, что этот комментарий от @rozzzly работает с TS 4.1.0 каждую ночь!

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type Url = `${'http'|'https'}://${Domain}`;

const success: Url = 'https://example.com';
const fail: Url = 'example.com';
const domain: Domain = 'example.com';

Попробуйте это на игровой площадке и увидите, что fail имеет ошибку времени компиляции 🤩

Это решило бы проблему данных или арии, с которой большинство из нас сталкивается в библиотеках UX, если бы ее можно было применить к индексам.

В основном это, но очевидно, что это не работает, потому что TS допускает только строку | количество. Поскольку это, по сути, строка, можно ли ее включить?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Update_: немного поиграв с этой функцией, она не охватит многие варианты использования. Например, это не работает для шестнадцатеричной цветовой строки.

type HexChar = '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type HexColor = `#${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}`;
let color: HexColor = '#123456';

Сегодня это не удается: «Выражение создает тип объединения, который слишком сложен для представления (2590)».

В примечаниях к выпуску есть ссылка на это ограничение. Он создает список всех возможных допустимых комбинаций, в этом случае он создаст объединение с 16 777 216 (т. Е. 16 ^ 6) членами.

Это отличная идея ... Игмат сделал несколько невероятных постов в 2016 году, которые в любом случае хорошо выглядят на бумаге.

Я нашел это, потому что хотел убедиться, что ключи литерала объекта, переданного в мою функцию, были действительными именами классов css. Я могу легко проверить во время выполнения ... но мне кажется настолько очевидным, что машинописный текст должен иметь возможность делать это во время компиляции, особенно в ситуациях, когда я просто жестко кодирую объектные литералы, а машинописный текст не должен определять, если MyUnionExtendedExotictype удовлетворяет SomeArbitraryRegexType.

Возможно, однажды я буду достаточно осведомлен, чтобы внести более продуктивный вклад: /

Я подтвердил, что этот комментарий от @rozzzly работает с TS 4.1.0 каждую ночь!

Вот это да. Честно говоря, я не ожидал, что это будет реализовано, по крайней мере, в ближайшее время.

@ chadlavi-casebook

В примечаниях к выпуску есть ссылка на это ограничение. Он создает список всех возможных допустимых комбинаций, в этом случае он создаст объединение с 16 777 216 (т. Е. 16 ^ 6) членами.

Мне было бы любопытно посмотреть, насколько большим может стать этот союз, прежде чем он станет проблемой с точки зрения производительности. Пример @styfle показывает, насколько легко достичь этого потолка. Очевидно, будет некоторая степень уменьшения отдачи от использования сложных типов по сравнению с производительностью.

@thehappycheese

Я хотел убедиться, что ключи литерала объекта, переданного в мою функцию, были действительными именами классов css

Я с уверенностью могу сказать, что в текущей реализации это невозможно. Если бы была поддержка квантификаторов и диапазонов, вы, вероятно, получили бы валидацию имен классов в стиле БЭМ. Стандартное регулярное выражение js для этого не слишком ужасно:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
Вы также можете отказаться от якорей, потому что в соответствии с реализацией это либо полное совпадение, либо ничего, поэтому подразумеваются ^ и $ . Теперь это сравнительно простое регулярное выражение для узкого подмножества того, что является допустимым селектором CSS. Например: ಠ_ಠ - допустимое имя класса. Я не шучу.

Мне жаль. Я должен был это сделать.

Я реализовал обычные языки в TypeScript.

Точнее, я реализовал простой детерминированный конечный автомат с помощью TS 4.1.

Я имею в виду, что мы уже можем реализовать машины Тьюринга в TS. Итак, DFA и КПК по сравнению с этим «просты».

А строки шаблона делают это более удобным.


Основные типы на самом деле простые и подходят для <30 LOC,

type Head<StrT extends string> = StrT extends `${infer HeadT}${string}` ? HeadT : never;

type Tail<StrT extends string> = StrT extends `${string}${infer TailT}` ? TailT : never;

interface Dfa {
    startState : string,
    acceptStates : string,
    transitions : Record<string, Record<string, string>>,
}

type AcceptsImpl<
    DfaT extends Dfa,
    StateT extends string,
    InputT extends string
> =
    InputT extends "" ?
    (StateT extends DfaT["acceptStates"] ? true : false) :
    AcceptsImpl<
        DfaT,
        DfaT["transitions"][StateT][Head<InputT>],
        Tail<InputT>
    >;

type Accepts<DfaT extends Dfa, InputT extends string> = AcceptsImpl<DfaT, DfaT["startState"], InputT>;

Самая сложная часть - это определение автоматов.

Но я почти уверен, что кто-то может сделать регулярное выражение для генератора TypeScript DFA ™ ...


Я также хотел бы подчеркнуть, что пример «шестнадцатеричной строки длины 6» показывает, что вы можете сделать так, чтобы параметры функции принимали только строки, соответствующие регулярному выражению, с помощью уродливого хакерства,

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

//OK
takesOnlyHex("DEADBE")

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

//OK
takesOnlyHex("01A34B")

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

Вот бонусная площадка ; он реализует регулярное выражение /^hello .*/

И еще одна детская площадка ; он реализует регулярное выражение / world$/

Последний пример: игровая площадка ; это строковое регулярное выражение с

@AnyhowStep Хорошо, я использовал вашу идею DFA для реализации простого регулярного выражения [abc]{4} что означает буквы abc в любом порядке с отсутствующими, но точно длиной 4. (aaaa, abcc, bbcc и т. Д.).
Детская площадка

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

https://github.com/CyberZHG/toolbox

Если бы у меня было больше силы воли, я бы взял что-то вроде приведенного выше и использовал бы его, чтобы превратить регулярные выражения в TS DFA ™ lol

Хорошо, я только что собрал прототип,

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

[Edit] https://glitch.com/~efficacious-valley-repair <- это дает лучший результат для более сложных регулярных выражений.

[Edit] Похоже, что Glitch будет архивировать бесплатные проекты, которые слишком долго неактивны. Итак, вот репозиторий git с файлами,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Шаг 1, введите здесь свое регулярное выражение,
image

Шаг 2, нажмите конвертировать,
image

Шаг 3, щелкните сгенерированный URL-адрес игровой площадки TS,
image

Шаг 4, прокрутите вниз до InLanguage_0 ,
image

Шаг 5, поиграйте с входными значениями,
image

image

Привет @kpdyer , автору https://www.npmjs.com/package/regex2dfa , за тяжелую работу по преобразованию.

Если кому-то нужно что-то посильнее, вот машина Тьюринга 😆

Детская площадка

Эта цепочка стала слишком длинной для чтения, и многие комментарии либо адресованы типам литералов шаблонов, либо не по теме. Я создал новую проблему # 41160 для обсуждения оставшихся вариантов использования, которые могут быть включены с помощью этой функции. Не стесняйтесь продолжить обсуждение парсеров системы типов здесь 😀

Была ли эта страница полезной?
0 / 5 - 0 рейтинги