Я придумал это громоздкое регулярное выражение. Я искренне думаю, что регулярное выражение может быть не лучшей идеей для этой задачи. Но было забавно выполнить разбивку
^((?=[^-_]*-[^-_]{3,}-[^-_]*$)|(?=[^-_]*-[^-_]{3,}_[^-_]*$)|(?=[^-_]*_[^-_]{3,}-[^-_]*$)|(?=[^-_]*[-_][^-_]*$)|[^-_\n]+$)[\w-]*$
, которую мы сопоставляем с каждым [\w-]*
(любым количеством слов, включая символы подчеркивания и да sh) с ограничениями, которые должны быть одним из:
(?=[^-_]*-[^-_]{3,}-[^-_]*$)
две черты, по крайней мере, с 3 символами между
(?=[^-_]*-[^-_]{3,}_[^-_]*$)
da sh и подчеркивание, по крайней мере, с 3 символами между
(?=[^-_]*_[^-_]{3,}-[^-_]*$)
подчеркиванием и da sh не менее 3 символов между
(?=[^-_]*[-_][^-_]*$)
только один da sh или подчеркивание
[^-_\n]+$
no da sh или подчеркивание
demo https://regex101.com/r/3MdB5Q/4
Как вы понимаете, при таком подходе вряд ли можно расширить другие ограничения, такие как "макс. 3 черточки". Причина в том, что регулярные выражения на самом деле не инструмент, когда вам нужно считать. Вы можете сделать это с помощью хитрости, но, как вы видите, это трудно расширить, а также читать для других разработчиков. Так что лучше воспользуйтесь некоторыми подсчетными функциями языка по вашему выбору.