Angular js Регулярное выражение в директиве валидации вызывает ошибку Error: too much recursion

У меня директива, проверяющая имя хоста с регулярным выражением: /^(xn--)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_][a-zA-Z0-9])\.?)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-][A-Za-z0-9])*$/ когда количество символов > 25 в поле которое проверяем и дописываем любой символ, например %, браузер зависает. С буквенными символами проблем не возникает, проблемы нету также при маленьком количестве символов. Подскажите, пожалуйста что не так.


Ответы (1 шт):

Автор решения: vsemozhebuty

Если не ошибаюсь, вы столкнулись с явлением "Катастрофический возврат". Описание и пути решения можно посмотреть тут: https://learn.javascript.ru/regexp-catastrophic-backtracking

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

Чтобы регулярка была чуть менее запутанная, я сначала попробовал её сократить: добавил флаг игнорирования регистра, чтобы можно было использовать только a-z, числовой диапазон заменил на \d.

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

Наконец, окружил первую большую группу повторений предложенным заглядыванием вперёд: (?=(группа))\1.

Получается что-то такое:

const re = /^(?:xn--)?(?=((?:(?:[a-z\d]|[a-z\d][a-z\d_-][a-z\d])\.?)+))\1(?:[a-z\d]|[a-z\d][a-z\d-][a-z\d])*$/i;

console.log(re.test('example.example.example.example.example.example.example.example.org%'));

Ещё мне вот что пришло в голову. Поскольку у вас очень ограниченный набор разрешённых символов, а сложность только в разных допустимых их сочетаниях, вы можете помочь движку регулярок, вставив в начало проверку с заглядыванием вперёд до конца, в которой укажите разрешённые символы: /^(?=[\w.-]+$)дальше-ваша-регулярка/i. Тогда многие тупиковые варианты будут отметаться с ходу:

const re = /^(?=[\w.-]+$)(xn--)?(([a-z\d]|[a-z\d][a-z\d_-][a-z\d])\.?)+([a-z\d]|[a-z\d][a-z\d-][a-z\d])*$/i;

console.log(re.test('example.example.example.example.example.example.example.example.org%'));

→ Ссылка