Ограничения regex-масок

Regex-маски в библиотеке Inputmask позволяют описывать сложные правила ввода через регулярные выражения, однако механизм их работы существенно отличается от полноценной обработки regex в JavaScript. Внутренний движок Inputmask анализирует ввод посимвольно, формируя допустимое состояние маски на каждом этапе ввода. Из-за этого возникают ограничения, которые отсутствуют при обычной проверке строки через RegExp.

Важно понимать ключевой принцип:

/^\d{4}$/

В обычном JavaScript это выражение проверяет уже готовую строку.

В Inputmask:

Inputmask({
    regex: "\\d{4}"
}).mask("#code");

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

Это фундаментально меняет поведение regex.


Пошаговая природа валидации

Inputmask не получает готовое значение целиком. Пользователь вводит символы постепенно:

1
12
123
1234

На каждом шаге движок обязан определить:

  • допустим ли текущий символ;
  • можно ли продолжать ввод;
  • существует ли потенциально валидное завершение строки.

Из-за этого некоторые конструкции регулярных выражений становятся несовместимыми с механизмом маскирования.


Ограниченная поддержка lookahead

Положительный просмотр вперёд:

(?=...)

поддерживается только в простых сценариях.

Пример:

Inputmask({
    regex: "(?=.*[A-Z]).*"
}).mask("#field");

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

Однако при вводе:

a
ab
abc

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

Результат зависит от:

  • позиции курсора;
  • режима greedy;
  • промежуточных состояний;
  • внутреннего backtracking движка.

На практике сложные lookahead-конструкции работают нестабильно.


Проблемы с lookbehind

Lookbehind:

(?<=...)
(?<!...)

поддерживается значительно хуже.

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

Пример:

Inputmask({
    regex: "(?<=USD)\\d+"
});

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

Во многих случаях lookbehind:

  • не работает;
  • работает частично;
  • приводит к зависанию курсора;
  • ломает удаление символов;
  • вызывает некорректный revalidation.

Ограничения якорей начала и конца строки

Якоря:

^
$

в обычном regex обозначают начало и конец строки.

В Inputmask строка постоянно находится в промежуточном состоянии.

Пример:

Inputmask({
    regex: "^\\d{5}$"
});

Во время ввода:

1
12
123

значение ещё не завершено.

Из-за этого Inputmask вынужден ослаблять строгую проверку.

Поведение может отличаться:

  • при вставке текста;
  • при удалении;
  • при jitMasking;
  • при использовании clearIncomplete.

Неполная поддержка backtracking

Regex-движки JavaScript используют сложный механизм возвратов (backtracking).

Пример:

(a|ab)c

Обычный regex способен:

  1. проверить a;
  2. понять, что вариант не подошёл;
  3. вернуться назад;
  4. попробовать ab.

Inputmask работает иначе.

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

Поэтому сложные альтернативы:

(a|ab|abc|abcd)

могут приводить к:

  • неправильному позиционированию курсора;
  • пропуску символов;
  • ложной блокировке ввода;
  • ухудшению производительности.

Ограничения вложенных групп

Сложные конструкции:

((ab(cd|ef))+|(xy(z|w))+)

резко увеличивают сложность обработки.

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

Симптомы:

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

Особенно критично это становится при:

  • длинных строках;
  • большом количестве optional-блоков;
  • динамических масках;
  • live validation.

Проблемы жадных квантификаторов

Конструкции:

.*
.+
[a-z]+

опасны для Inputmask.

Причина заключается в неопределённой длине.

Пример:

Inputmask({
    regex: ".*@.*"
});

Такой шаблон теоретически допускает бесконечное количество состояний.

Inputmask вынужден постоянно пересчитывать:

  • допустимые позиции;
  • потенциальное завершение строки;
  • размещение курсора.

Это вызывает нестабильность.

Гораздо безопаснее:

Inputmask({
    regex: "[^@]{1,50}@[a-z]{2,20}\\.[a-z]{2,10}"
});

Катастрофический backtracking

Некоторые regex-конструкции способны вызывать катастрофическое количество проверок.

Классический пример:

(a+)+

или:

([a-z]+)*

В обычном JavaScript это уже потенциальная проблема.

В Inputmask ситуация усугубляется тем, что проверка происходит после каждого символа.

Даже строка длиной 20–30 символов может вызывать:

  • фризы интерфейса;
  • скачки CPU;
  • зависание вкладки браузера.

Ограничения рекурсивных шаблонов

Настоящая рекурсия regex:

(?R)

или аналогичные PCRE-конструкции не поддерживаются вообще.

Inputmask использует JavaScript RegExp, а JavaScript не поддерживает рекурсивные regex.

Поэтому невозможно корректно описать:

  • вложенные скобки произвольной глубины;
  • XML-подобные структуры;
  • рекурсивные языки.

Ограничения Unicode regex

JavaScript поддерживает Unicode-флаги:

/u

Однако Inputmask не всегда корректно взаимодействует с Unicode-классами.

Проблемный пример:

\p{L}

или:

\p{Script=Cyrillic}

Причины:

  • зависимость от браузера;
  • особенности внутреннего parser Inputmask;
  • несовместимость с отдельными версиями движка.

Некоторые Unicode-конструкции:

  • игнорируются;
  • ломают позиционирование;
  • работают только частично.

Ограничения при вставке текста

Paste-события значительно сложнее обычного ввода.

При вставке строки:

+7 (777) 123-45-67

Inputmask вынужден:

  1. обработать каждый символ;
  2. проверить промежуточные состояния;
  3. синхронизировать буфер маски.

Сложные regex ухудшают paste-производительность многократно.

Особенно опасны:

(.+)*
(.*)+
(\w+)+

Несовместимость с некоторыми regex-флагами

Флаги Jav * aScript:

g
m
s
y
u

не используются в Inputmask напрямую.

Regex передаётся как строка:

regex: "\\d+"

а не как объект RegExp.

Поэтому многие привычные возможности недоступны.

Например:

/\d+/g

не имеет смысла внутри Inputmask.


Ограничения multiline-логики

Inputmask ориентирован на однострочный ввод.

Конструкции:

^
$

в multiline-сценариях ведут себя непредсказуемо.

Переносы строк:

\n

обычно несовместимы с классическими input-полями.

Regex для textarea работают ограниченно и требуют отдельного тестирования.


Проблемы с optional-группами

Слишком большое количество optional-блоков:

(a)?(b)?(c)?(d)?(e)?

создаёт огромное количество возможных состояний.

Inputmask вынужден хранить и проверять варианты:

a
ab
abc
abd
acd
...

Количество комбинаций растёт экспоненциально.

Последствия:

  • торможение ввода;
  • ошибки caret-позиции;
  • случайное удаление символов;
  • неправильное автозаполнение.

Ограничения динамической длины

Regex вроде:

\d+

не имеют верхней границы.

Для Inputmask это плохо.

Движок не знает:

  • сколько символов допустимо;
  • где завершать маску;
  • как вычислять tail-позиции.

Рекомендуется всегда задавать диапазон:

\d{1,10}

Невозможность полноценного контекстного анализа

Regex-маски плохо подходят для сценариев, где значение зависит от сложного контекста.

Например:

февраль → максимум 28 дней
апрель → максимум 30 дней

Теоретически regex можно сделать крайне сложным:

(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])

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

Для таких случаев лучше использовать:

  • postValidation;
  • onBeforePaste;
  • пользовательские validator-функции;
  • отдельную бизнес-валидацию.

Ограничения производительности

Сложность regex напрямую влияет на отзывчивость интерфейса.

Inputmask выполняет:

  • анализ;
  • revalidation;
  • позиционирование;
  • backtracking;
  • перерасчёт буфера

после каждого действия пользователя.

Даже относительно небольшой regex может стать проблемой:

([a-zA-Z0-9._%+-]+)+@([a-z0-9.-]+)+\.[a-z]{2,}

Особенно на:

  • мобильных устройствах;
  • старых браузерах;
  • слабых CPU;
  • длинных формах.

Различие между validation и masking

Главная ошибка при работе с regex-масками — попытка использовать Inputmask как полноценный валидатор.

Маскирование и финальная валидация — разные задачи.

Inputmask хорошо подходит для:

  • ограничения формата;
  • направления ввода;
  • подсказки структуры;
  • предварительной фильтрации.

Но сложную бизнес-валидацию лучше выполнять отдельно:

const isValid = complexRegex.test(value);

после завершения ввода.


Практические рекомендации

Ограничивать длину

Плохо:

.*

Хорошо:

.{1,50}

Избегать глубоких альтернатив

Плохо:

(a|ab|abc|abcd|abcde)

Хорошо:

ab(c(de)?)?

Минимизировать optional-группы

Плохо:

(a)?(b)?(c)?(d)?

Хорошо:

(a(b(c(d)?)?)?)?

Не использовать regex как бизнес-движок

Плохо:

regex: "сверхсложное_правило_на_300_символов"

Хорошо:

Inputmask({
    regex: "\\d{2}\\.\\d{2}\\.\\d{4}",
    postValidation(buffer) {
        return validateDate(buffer.join(""));
    }
});

Избегать потенциально бесконечных шаблонов

Опасно:

(.+)*

Безопаснее:

.{0,100}

Типичные ошибки при проектировании regex-масок

Попытка полностью заменить backend-валидацию

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


Использование regex из backend без адаптации

Regex для:

  • PCRE;
  • Python;
  • .NET;
  • Java

часто несовместимы с Inputmask.


Игнорирование промежуточных состояний

Regex может быть валиден только в финальном виде, но Inputmask обязан поддерживать все этапы ввода.


Чрезмерная сложность шаблона

Чем сложнее regex, тем хуже:

  • UX;
  • производительность;
  • поддерживаемость кода.

Когда regex-маски действительно эффективны

Regex-маски особенно полезны для:

  • PIN-кодов;
  • индексов;
  • артикулов;
  • простых email;
  • кодов подтверждения;
  • номеров документов;
  • фиксированных идентификаторов.

Примеры удачных шаблонов:

\\d{4}
[A-Z]{2}\\d{6}
[0-9]{5}
[a-z0-9._%+-]{1,30}@[a-z0-9.-]{1,30}\\.[a-z]{2,6}

В этих случаях Inputmask работает стабильно, быстро и предсказуемо.