reedach 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE.md CHANGED
@@ -7,11 +7,18 @@
7
7
  [Максим Ильяхов](https://maximilyahov.ru), [Бюро Горбунова](https://bureau.ru) и другие авторы.
8
8
  Тексты статей в проект не копировались.
9
9
 
10
+ Часть пар «англицизм → русское слово» выписана из набора
11
+ [ru_anglicism](https://huggingface.co/datasets/shershen/ru_anglicism) (Apache-2.0): из него
12
+ взяты только сами слова и замены, предложения набора в проект не входят. Список паронимов
13
+ сверен со словариком паронимов ФИПИ для ЕГЭ, пояснения к ним написаны заново. Идеи части
14
+ проверок типографики взяты из [typograf](https://github.com/typograf/typograf) (MIT,
15
+ © Денис Селезнёв), регулярные выражения написаны для Reedach.
16
+
10
17
  Проверка не заменяет профессиональную редактуру.
11
18
 
12
19
  ## Сторонние данные и программы
13
20
 
14
- Ядро (`packages/core`) не содержит сторонних данных и зависимостей.
21
+ Ядро (`packages/core`) не содержит сторонних зависимостей. О словах из открытых наборов — выше.
15
22
 
16
23
  Сервер (`packages/server`) использует их как зависимости, не включая в этот репозиторий:
17
24
 
package/README.md CHANGED
@@ -17,7 +17,8 @@ import {checkStyle, sourceBlocks, mergeIssues} from 'reedach';
17
17
  const markdown = 'В настоящее время мы осуществляем доставку хлеба.';
18
18
  const blocks = sourceBlocks(markdown);
19
19
  const issues = mergeIssues(blocks, {local: checkStyle(blocks)});
20
- // [{start, end, id, category, label, message, fix}, …] — start и end указывают в исходный текст
20
+ // [{start, end, id, category, label, message, fix, replace?}, …] — start и end указывают в исходный текст,
21
+ // replace — варианты замены, если они есть
21
22
  ```
22
23
 
23
24
  Без Markdown: `checkStyle([{text: '…', kind: 'p'}])`, где `kind` — `p` (абзац), `li` (пункт
@@ -26,4 +27,6 @@ maxParagraphWords: 120})`.
26
27
 
27
28
  Словарь правил, источники и лицензии — в README и NOTICE.md [репозитория проекта](https://github.com/dimafaller-en/reedach).
28
29
 
30
+ История версий всех пакетов — в [CHANGELOG.md](https://github.com/dimafaller-en/reedach/blob/main/CHANGELOG.md).
31
+
29
32
  MIT, © 2026 Дима Фаллер.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reedach",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Лёгкая проверка русского текста на канцелярит, штампы и воду: словарь правил, работает в браузере и в Node без зависимостей",
5
5
  "license": "MIT",
6
6
  "author": "Dima Faller <dima@dimafaller.ru>",
package/src/rules.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Our own style rules, written from public advice on information style.
2
- export const CATEGORIES={borrowing:'Заимствование',generalization:'Обобщение',euphemism:'Эвфемизм',preachy:'Назидание',filler:'Вода',slang:'Жаргон',stamp:'Штамп',bureaucratese:'Канцелярит',vague:'Неконкретно',evaluation:'Оценка',modality:'Модальность',intro:'Вводное',time:'Паразит времени',pleonasm:'Плеоназм',pronoun:'Местоимение',adjective:'Прилагательное',kotory:'«Который»',syntax:'Синтаксис',passive:'Страдательный залог',nominalization:'Отглагольное',repetition:'Повтор',length:'Длина',numbers:'Числа',structure:'Структура',mistake:'Частая ошибка',colloquial:'Просторечие',typography:'Типографика'};
2
+ export const CATEGORIES={paronym:'Пароним',borrowing:'Заимствование',generalization:'Обобщение',euphemism:'Эвфемизм',preachy:'Назидание',filler:'Вода',slang:'Жаргон',stamp:'Штамп',bureaucratese:'Канцелярит',vague:'Неконкретно',evaluation:'Оценка',modality:'Модальность',intro:'Вводное',time:'Паразит времени',pleonasm:'Плеоназм',pronoun:'Местоимение',adjective:'Прилагательное',kotory:'«Который»',syntax:'Синтаксис',passive:'Страдательный залог',nominalization:'Отглагольное',repetition:'Повтор',length:'Длина',numbers:'Числа',structure:'Структура',mistake:'Частая ошибка',colloquial:'Просторечие',typography:'Типографика'};
3
3
  export const RULES=[
4
4
  // Time parasites
5
5
  {id:'time-parasite-start',category:'time',start:true,message:'Вступление о нынешнем времени ничего не добавляет к предложению.',fix:'Убрать его или назвать конкретную дату или факт.',words:['в настоящее время','в настоящий момент','в наши дни','в наше время','в последнее время','в последние годы','нынче','до недавнего времени','в современном мире','в современных условиях']},
@@ -34,15 +34,16 @@ export const RULES=[
34
34
  // Pronoun
35
35
  {id:'pronoun-dannyy',category:'pronoun',message:'Слово данный звучит канцелярски и ничего не уточняет.',fix:'Заменить на слово этот или назвать предмет прямо.',words:['данный','данная','данное','данную','данной','данного','данном','данному']},
36
36
  // Bureaucratese and nominalization
37
- {id:'bureaucratese-verbs',category:'bureaucratese',message:'Канцелярский оборот звучит как отписка и скрывает суть.',fix:'Заменить простым глаголом или словом: делать, для, чтобы, это.',words:['осуществл*','имеет место','имеют место','имел место','имело место','в рамках','в целях','с целью','на предмет','посредством','является','являются','являться','вышеуказанный','вышеупомянутый','нижеследующий']},
38
- {id:'bureaucratese-latinisms',category:'bureaucratese',message:'Иностранное слово там, где есть простое русское, затрудняет чтение.',fix:'Заменить обычным русским словом.',swap:{'коммуникац':'общение, связь','компетенц':'умение, знания, круг обязанностей','минимизир':'сократить, уменьшить','минимизац':'сокращение, уменьшение','конкретизир':'уточнить','релевантн':'подходящий, нужный, уместный','нерелевантн':'неподходящий, лишний','конкурентоспособн':'не уступает другим, выгоднее','стагнац':'застой','конъюнктур':'положение на рынке, спрос','деградац':'упадок, ухудшение','имплементир':'внедрить, ввести','имплементац':'внедрение','консолидир':'объединить, собрать','консолидац':'объединение','синерги':'совместная польза, выгода от объединения','функционал':'возможности, функции'},words:['коммуникац*','компетенц*','минимизир*','минимизац*','конкретизир*','релевантн*','нерелевантн*','конкурентоспособн*','стагнац*','конъюнктур*','деградац*','имплементир*','имплементац*','консолидир*','консолидац*','синерги*','функционал']},
39
- {id:'nominalization-support-verbs',category:'nominalization',message:'Глагол плюс существительное длиннее и тяжелее одного глагола.',fix:'Заменить одним глаголом: монтировать вместо производить монтаж.',words:['принимать участие','принять участие','принял участие','приняли участие','принимаю участие','принимаем участие','принимает участие','принимают участие','оказать помощь','оказывать помощь','оказали помощь','оказываем помощь','оказание помощи','оказать услуги','оказывать услуги','оказали услуги','оказываем услуги','оказание услуг','производить монтаж','произвести монтаж','произвели монтаж','производим монтаж','производить замену','произвести замену','произвели замену','производим замену','производить ремонт','произвести ремонт','произвели ремонт','производим ремонт','проводить работы','проводим работы','проводят работы','провести работы','провели работы']},
37
+ {id:'bureaucratese-verbs',category:'bureaucratese',message:'Канцелярский оборот звучит как отписка и скрывает суть.',fix:'Заменить простым глаголом или словом: делать, для, чтобы, это.',swap:{'осуществлени':'выполнение, проведение','осуществл':'делать, проводить, выполнять','имеет место':'есть, происходит','имеют место':'есть, происходят','имел место':'был, произошёл','имело место':'было, произошло','вышеуказанный':'этот, указанный','вышеупомянутый':'этот, упомянутый','нижеследующий':'следующий'},hints:{'в рамках':'Заменить предлогом: в, по, внутри.','в целях':'Заменить: для, чтобы.','с целью':'Заменить: чтобы, для.','на предмет':'Заменить: о, на, чтобы проверить.','посредством':'Заменить: через, с помощью.','являет':'Убрать слово: Москва — столица, а не Москва является столицей.','являть':'Убрать слово: Москва — столица, а не Москва является столицей.'},words:['осуществл*','имеет место','имеют место','имел место','имело место','в рамках','в целях','с целью','на предмет','посредством','является','являются','являться','вышеуказанный','вышеупомянутый','нижеследующий']},
38
+ {id:'bureaucratese-latinisms',category:'bureaucratese',message:'Иностранное слово там, где есть простое русское, затрудняет чтение.',fix:'Заменить обычным русским словом.',swap:{'коммуникац':'общение, связь','компетенц':'умение, знания, круг обязанностей','минимизир':'сократить, уменьшить','минимизац':'сокращение, уменьшение','конкретизир':'уточнить','релевантн':'подходящий, нужный, уместный','нерелевантн':'неподходящий, лишний','конкурентоспособн':'выгодный, не уступает другим','стагнац':'застой','конъюнктур':'положение на рынке, спрос','деградац':'упадок, ухудшение','имплементир':'внедрить, ввести','имплементац':'внедрение','консолидир':'объединить, собрать','консолидац':'объединение','синерги':'совместная польза, выгода от объединения','функционал':'возможности, функции'},words:['коммуникац*','компетенц*','минимизир*','минимизац*','конкретизир*','релевантн*','нерелевантн*','конкурентоспособн*','стагнац*','конъюнктур*','деградац*','имплементир*','имплементац*','консолидир*','консолидац*','синерги*','функционал']},
39
+ {id:'nominalization-support-verbs',category:'nominalization',message:'Глагол плюс существительное длиннее и тяжелее одного глагола.',fix:'Заменить одним глаголом: монтировать вместо производить монтаж.',exact:true,swap:{'принимать участие':'участвовать','принять участие':'поучаствовать','принял участие':'участвовал','приняли участие':'участвовали','принимаю участие':'участвую','принимаем участие':'участвуем','принимает участие':'участвует','принимают участие':'участвуют','оказать помощь':'помочь','оказывать помощь':'помогать','оказали помощь':'помогли','оказываем помощь':'помогаем','производить монтаж':'монтировать','произвести монтаж':'смонтировать','произвели монтаж':'смонтировали','производим монтаж':'монтируем','производить замену':'заменять','произвести замену':'заменить','произвели замену':'заменили','производим замену':'заменяем','производить ремонт':'ремонтировать','произвести ремонт':'отремонтировать','произвели ремонт':'отремонтировали','производим ремонт':'ремонтируем'},hints:{'оказание помощи':'Заменить: помощь, помочь.','оказ':'Назвать работу глаголом: помочь, сделать, настроить.','провод':'Назвать работу глаголом: красить, монтировать, чинить.','провел':'Назвать работу глаголом: покрасили, смонтировали, починили.','провест':'Назвать работу глаголом: покрасить, смонтировать, починить.'},words:['принимать участие','принять участие','принял участие','приняли участие','принимаю участие','принимаем участие','принимает участие','принимают участие','оказать помощь','оказывать помощь','оказали помощь','оказываем помощь','оказание помощи','оказать услуги','оказывать услуги','оказали услуги','оказываем услуги','оказание услуг','производить монтаж','произвести монтаж','произвели монтаж','производим монтаж','производить замену','произвести замену','произвели замену','производим замену','производить ремонт','произвести ремонт','произвели ремонт','производим ремонт','проводить работы','проводим работы','проводят работы','провести работы','провели работы']},
40
40
  // Stamps
41
41
  {id:'stamp-cliche',category:'stamp',message:'Оборот затёрт: читатель видит штамп раньше смысла.',fix:'Написать конкретный факт или действие без оборота.',words:['сочета* в себе','на каждом шагу','в сухом остатке','на помощь приходят','на помощь приходит','легко и просто','без промедления','прямиком','расширить географию','расширили географию','играет важную роль','играют важную роль','играет ключевую роль','играют ключевую роль','играет большую роль','играют большую роль','как показывает практика','на высоком уровне','на должном уровне','на новый уровень','время покажет']},
42
- {id:'stamp-newspaper-idioms',category:'stamp',message:'Газетный оборот давно затёрт и подменяет смысл образом.',fix:'Сказать то же простыми словами: быстро, в конце, правильно.',words:['сломя голову','семимильн* шаг*','аж пятки сверкали','финальн* аккорд*','в нужном русле','сомнительн* удовольствие*','ярк* штрих*','в лучших традициях','испокон веков','достойн* лучшего применения','по большому счету','на всех парах','в двух словах','тернист* пут*']},
42
+ {id:'stamp-newspaper-idioms',category:'stamp',message:'Газетный оборот давно затёрт и подменяет смысл образом.',fix:'Сказать то же простыми словами: быстро, в конце, правильно.',exact:true,swap:{'сломя голову':'быстро, наспех','семимильн':'быстро','аж пятки сверкали':'быстро','в нужном русле':'правильно, как задумано','испокон веков':'всегда, давно','по большому счету':'в целом, в итоге','на всех парах':'быстро, полным ходом','в двух словах':'коротко, кратко'},hints:{'финальн':'Сказать прямо: финал, завершение, итог.','сомнительн':'Сказать прямо: неприятно, плохо, невыгодно.','ярк':'Назвать саму деталь или пример.','в лучших традициях':'Сказать прямо: как принято, по традиции.','достойн':'Назвать, на что лучше потратить силы.','тернист':'Сказать прямо: трудный путь, долгая работа.'},words:['сломя голову','семимильн* шаг*','аж пятки сверкали','финальн* аккорд*','в нужном русле','сомнительн* удовольствие*','ярк* штрих*','в лучших традициях','испокон веков','достойн* лучшего применения','по большому счету','на всех парах','в двух словах','тернист* пут*']},
43
43
  {id:'stamp-sales',category:'stamp',message:'Рекламная формула звучит одинаково у всех и ничего не доказывает.',fix:'Показать, чем это выражается: срок, цифра, пример из работы.',words:['индивидуальн* подход*','широк* спектр*','широк* ассортимент*','команда профессионалов','команды профессионалов','высококвалифицированн*','богат* опыт*','многолетн* опыт*','современн* технолог*','лидер рынка','лидеры рынка','клиентоориентированн*','комплексн* подход*','комплексн* решени*','динамично развивающ*']},
44
44
  // Pleonasms
45
- {id:'pleonasm-pairs',category:'pleonasm',message:'Одно слово повторяет смысл другого.',fix:'Оставить одно слово, которое несёт смысл.',words:['повтор* снова','повтор* еще раз','предварительн* предоплат*','подн* вверх','спуск* вниз','спуст* вниз','вернуть* назад','вернул* назад','возвраща* назад','главн* приоритет*','свободн* ваканси*','сво* собственн*','совместн* сотрудничеств*','взаимн* сотрудничеств*','внезапно вдруг','вдруг внезапно','каждый день ежедневно','ежедневно каждый день','краткое резюме','основной лейтмотив','первый дебют','стеклянн* остеклени*']},
45
+ {id:'pleonasm-pairs',category:'pleonasm',message:'Одно слово повторяет смысл другого.',fix:'Оставить одно слово, которое несёт смысл.',exact:true,swap:{'тестов* испытани*':'$2','отличительн* особенност*':'$2, отличительные черты','коротк* мгновени*':'$2','мясо свинины':'свинина','мясо говядины':'говядина','равн* половин*':'$2','cd-диск':'CD, диск','dvd-диск':'DVD, диск','памятн* сувенир*':'$2','вернуть* обратно':'$1','вернул* обратно':'$1','возвраща* обратно':'$1','опуска* вниз':'$1','полн* аншлаг*':'$2','ведущ* лидер*':'$2','впервые дебютир*':'$2','мимолетн* мгновени*':'$2','кратк* мгновени*':'$2','ценн* клад*':'$2','народн* фольклор*':'$2','местн* абориген*':'$2','патриот* родин*':'$1','в конечном итоге':'$1 $3','реальн* факт':'$2','реальн* факта':'факт','реальн* факты':'факт','реальн* фактов':'факт','реальн* фактом':'факт','реальн* фактами':'факт','бесплатн* подар*':'$2','бесплатн* бонус*':'$2','совместн* партнерств*':'$2','взаимодействи* между собой':'$1','взаимодейству* друг с другом':'$1','сотруднича* вместе':'$1','сотруднича* совместно':'$1','объедин* вместе':'$1','прайс-лист цен':'$1','наиболее лучш*':'$2','более лучш*':'$2, лучше','главн* суть':'$2','перспектив* на будущее':'$1','будущ* перспектив*':'$2','потенциальн* возможност*':'$2, потенциал','реальн* действительност*':'$2','мемориальн* памятник*':'$2, мемориал','внутренн* интерьер*':'$2','завершающ* финал*':'$2','заключительн* финал*':'$2','творческ* креатив*':'$2','дисконтн* скидк*':'$2, дисконт','имеет* в наличии':'есть, $2 $3','имеют* в наличии':'есть, $2 $3','возобнови* снова':'$1','возобнови* вновь':'$1','переделыва* заново':'$1','подарить подарок':'$1, сделать $2','рекламир* рекламу':'$1','спросить вопрос':'задать $2','спросил* вопрос*':'задать $2'},words:['повтор* снова','повтор* еще раз','предварительн* предоплат*','подн* вверх','спуск* вниз','спуст* вниз','вернуть* назад','вернул* назад','возвраща* назад','главн* приоритет*','свободн* ваканси*','сво* собственн*','совместн* сотрудничеств*','взаимн* сотрудничеств*','внезапно вдруг','вдруг внезапно','каждый день ежедневно','ежедневно каждый день','краткое резюме','основной лейтмотив','первый дебют','стеклянн* остеклени*','тестов* испытани*','отличительн* особенност*','коротк* мгновени*','мясо свинины','мясо говядины','равн* половин*','cd-диск','dvd-диск','памятн* сувенир*','вернуть* обратно','вернул* обратно','возвраща* обратно','опуска* вниз','полн* аншлаг*','ведущ* лидер*','впервые дебютир*','мимолетн* мгновени*','кратк* мгновени*','ценн* клад*','народн* фольклор*','местн* абориген*','патриот* родин*','в конечном итоге','реальн* факт','реальн* факта','реальн* факты','реальн* фактов','реальн* фактом','реальн* фактами','бесплатн* подар*','бесплатн* бонус*','совместн* партнерств*','взаимодействи* между собой','взаимодейству* друг с другом','сотруднича* вместе','сотруднича* совместно','объедин* вместе','прайс-лист цен','наиболее лучш*','более лучш*','главн* суть','перспектив* на будущее','будущ* перспектив*','потенциальн* возможност*','реальн* действительност*','мемориальн* памятник*','внутренн* интерьер*','завершающ* финал*','заключительн* финал*','творческ* креатив*','дисконтн* скидк*','имеет* в наличии','имеют* в наличии','возобнови* снова','возобнови* вновь','переделыва* заново','подарить подарок','рекламир* рекламу','спросить вопрос','спросил* вопрос*']},
46
+ {id:'paronyms',category:'paronym',message:'Похожее слово с другим значением: паронимы легко перепутать.',fix:'Проверить, какое слово нужно по смыслу.',pairs:[['одеть|одел*|оден*|одев*','пальто|куртк*|шапк*|шляп*|кепк*|очки|очков|перчатк*|варежк*|шарф*|шубу|плащ|плащ*|кроссовк*|туфл*|ботинки|сапоги|обувь|маск*|костюм|платье|кольцо|браслет|серьги|цепочк*|наушники|галстук|пиджак|футболк*|свитер|кофту|джинсы|брюки|юбку|ремень|часы|каску|шлем|форму|халат|жилет|бронежилет|парик|рюкзак|коньки|лыжи|ролики|рубашку|носки|колготки|фартук|наручники','после','надеть','Одевают кого-то, а надевают что-то на себя или другого.'],['надеть|надел*|наден*|надева*','ребёнка|ребенка|детей|малыша|младенца|куклу|больного|пациента|ребят','после','одеть','Надевают вещь, а одевают человека.'],['представить|представил*|представля*|представим|представит|представлю','доступ|отпуск|рассрочк*|отсрочк*|помещени*|консультаци*|кредит|парковк*|жильё|жилье','после','предоставить','Представить значит показать, а дать возможность — предоставить.'],['предоставить|предоставил*|предоставля*|предоставьте','вашему вниманию|вниманию читателей|на ваш суд|на ваше внимание','после','представить','Внимание и суд: показывают, то есть представляют.'],['предоставить|предоставил*|предоставля*|предоставьте','на рассмотрение|на утверждение|на согласование','после','представить','Документ на рассмотрение представляют, а не предоставляют.'],['оплатить|оплатил*|оплатит*|оплатим|оплатят|оплатите|оплачива*|оплачу|оплачивай*','за товар*|за услуг*|за заказ*|за доставк*|за проезд|за обучени*|за покупк*|за работ*|за аренд*|за номер|за билет*|за квартир*|за коммунал*|за свет|за интернет|за парковк*|за хранени*|за ремонт|за подписк*','после','оплатить что (или заплатить за что)','Оплачивают сам товар или услугу, а платят за них.'],['игра|играет|играют|играть|играл*|играя','значение|большое значение|важное значение|огромное значение|первостепенное значение|решающее значение|ключевое значение|особое значение|немалое значение|главное значение|существенное значение|значительное значение','после','имеет значение или играет роль','Значение имеют, а играют роль.'],['имеет|имеют|иметь|имел*|имея','роль|большую роль|важную роль|огромную роль|ключевую роль|значительную роль|решающую роль|основную роль','после','играет роль или имеет значение','Роль играют, а значение имеют.'],['оказывает|оказывают|оказать|оказывал*|оказал*','роль|большую роль|важную роль|значение|большое значение|важное значение','после','играет роль, имеет значение','Оказывают влияние, а роль играют.'],['абонент','купить|приобрести|годовой|в бассейн|в спортзал|в фитнес|в зал|на посещение|на занятия','любой','абонемент','Абонент — пользователь связи, а проходка в зал — абонемент.'],['благодарн*','письм*|грамот*|открытк*','после','благодарственный','Официальная благодарность: письмо благодарственное.'],['гарантированн*','талон*|случа*|письм*','после','гарантийный','Гарантийный талон и случай, а гарантированный — обеспеченный.'],['гарантийн*','результат*|доход*|прибыл*|эффект*|минимум*|приз*|заработ*','после','гарантированный','Гарантированный — обеспеченный; гарантийный относится к гарантии.'],['действенен|действенна|действенно|действенны','до','после','действителен','Срок документа: действителен, а действенный — результативный.'],['действительный|действительная|действительное|действительные|действительного|действительной|действительную|действительных','метод*|способ*|средств*|мер|меры|мерам|лечени*','после','действенный','Результативный метод — действенный, а действительный — подлинный.'],['длинн*','срок*|период*|перспектив*','после','долгий, длительный, долгосрочный','О времени говорят длительный или долгий, длинный — о протяжённости.'],['доверчив*','отношени*|разговор*|тон|беседа|беседы|атмосфер*|общение','после','доверительный','Доверительный разговор, а доверчивый — легко верящий человек.'],['дружеск*','интерфейс*|стран*|государств*','после','дружественный','Государство и интерфейс дружественные, дружеские — люди и отношения.'],['жесток*','диск*|дисков','после','жёсткий','Жёсткий диск, а жестокий — свирепый.'],['жилищн*','дом|дома|дому|домом|помещени*|район*|массив*','после','жилой','Жилой дом и жилое помещение; жилищный — про вопросы жилья.'],['зрительск*','зал*|нерв*|анализатор*|зрени*','после','зрительный','Зрительный зал и нерв, а зрительский — про публику.'],['зрительный|зрительная|зрительное|зрительные|зрительного|зрительной|зрительную|зрительных','аудитори*|симпати*|голосовани*|приз*|рейтинг*','после','зрительский','Зрительский приз и симпатии, а зрительный — про зрение.'],['информативн*','повод*|поддержк*|портал*|агентств*|безопасност*|войн*','после','информационный','Информационный повод, а информативный — содержательный.'],['исходящ*','код|кода|коде|кодом','после','исходный','Исходный код, а исходящий — отправляемый.'],['ледовый|ледовая|ледовое|ледовые|ледовой|ледовую|ледового|ледовых','вода|воды|воду|душ|ветер|ветра|взгляд*|голос|тон|дождь|напиток|напитки|кофе|чай','после','ледяной','Холодная вода ледяная, а ледовый — про лёд как покрытие.'],['ледяной|ледяная|ледяное|ледяные|ледяного|ледяной|ледяную|ледяных','арена|арены|арену|хоккей|шоу|побоище','после','ледовый','Ледовая арена и шоу; ледяной — очень холодный.'],['личностн*','кабинет*|аккаунт*|данн*|автомобил*|вещи|вещей|страниц*|мнение|мнения|бренд*|информаци*','после','личный','Личный кабинет, а личностный — про свойства личности.'],['нетерпимый|нетерпимая|нетерпимое|нетерпимые|нетерпимой|нетерпимую|нетерпимым','боль|боли|болью|жара|жару|жаре|холод|холода|запах|вонь|вонью|зуд|жажда|жажду|духота|духоту|шум','после','нестерпимый','Нестерпимую боль не вытерпеть, а нетерпимый — не терпящий чужого.'],['обвинен*|обвинён*','приговор*|акт|заключени*|реч*','после','обвинительный','Обвинительное заключение, а обвинённый — тот, кого обвиняют.'],['обрывок|обрывки|обрывков|обрывком','из книги|из романа|из повести|из произведения|из стихотворения|из пьесы','после','отрывок','Обрывок рвут, а из книги берут отрывок.'],['отрывок|отрывки|отрывка|отрывком','бумаги|ткани|нитки|верёвки|веревки|газеты|фольги','после','обрывок','Оторванный кусок бумаги и ткани — обрывок.'],['осудить|осудил*|осудим|осудите|осудят','детали|цену|цены|сроки|стоимость|график|сотрудничество|партнерство|партнёрство|перспективы','после','обсудить','Осудить значит осветить, обсудить — поговорить.'],['обхватить|обхватил*|обхватим|обхватит|обхватыва*','аудитори*|рынок|рынка|регион*|вопросы|темы|тему|весь спектр|клиентов|страны|территори*|направлени*','после','охватить','Охватывают аудиторию и темы, обхватывают руками.'],['отборн*','тур*|этап*|матч*|раунд*|турнир*|соревновани*|игры|испытани*','после','отборочный','Отборочный тур, а отборный — лучший по качеству.'],['отклонение|отклонения|отклонений','от налогов|от налога|от налогообложения|от уплаты|от службы|от ответственности|от призыва|от выплаты','после','уклонение','От налогов и службы уклоняются, а отклонение — от нормы.'],['уклонени*','от нормы|от курса|от плана|от графика|от стандарта|от стандартов|от параметров|от нормативов|от траектории|от маршрута','после','отклонение','Отклонение от нормы, а уклонение — увиливание.'],['отклониться|отклонился|отклонилась|отклонились|отклоняется|отклоняются|отклоняться','от налогов|от уплаты|от службы|от ответственности|от призыва|от ответа|от обязанностей','после','уклониться','От обязанностей уклоняются, а отклоняются от курса.'],['отличие|отличия|отличий|отличиях','между','после','различие между (или отличие от)','Различие — между чем-то, отличие — от чего-то.'],['различаются|различается|различались|различался|различалась|различаться','от','после','отличаться','Отличаются от чего-то, различаются между собой.'],['покупательск*','способност*','после','покупательный','Устойчивое сочетание: покупательная способность.'],['покупательн*','поведение|поведени*|привычк*|интерес*|аудитори*|потребност*|опыт|лояльност*','после','покупательский','Покупательское поведение и привычки; покупательный — про способность.'],['понятлив*','язык*|текст*|интерфейс*|объяснени*|инструкци*|изложени*|дизайн|сайт*|меню|формулировк*|правил*','после','понятный','Понятлив человек, а текст и интерфейс понятны.'],['почтенн*','караул*|звани*|грамот*|знак*|донор*','после','почётный','Почётный караул и звание, а почтенный — уважаемый по возрасту.'],['почётный|почетный|почётная|почетная|почётное|почетное','возраст|возраста|возрасту|возрастом|старость|старости','после','почтенный','Почтенный возраст: заслуживающий уважения.'],['практический|практическая|практическое|практические|практического|практической|практическую|практических','человек|люди|одежд*|обув*|вещь|вещи|подарок|подарки|мебель|сумк*|чехол*','после','практичный','Практичная вещь удобна, практический — относящийся к практике.'],['практичн*','опыт*|занят*|навык*|примен*|знани*|курс*|задач*|семинар*|значени*|пособи*|обучени*|стажировк*','после','практический','Практический опыт и занятия, а практичный — выгодный в быту.'],['скрытный|скрытная|скрытное|скрытные|скрытной|скрытную|скрытного|скрытных','камер*|реклам*|смысл*|угроз*|дефект*|потенциал*|платеж*|платёж*|комисси*|фактор*|проблем*|возможност*|резерв*|ресурс*','после','скрытый','Скрытый смысл, а скрытный — о замкнутом человеке.'],['техничн*','поддержк*|обслуживани*|специалист*|характеристик*|задани*|паспорт*|осмотр*|директор*|персонал*|документаци*|помощь|помощи|регламент*|служб*|отдел*','после','технический','Техническая поддержка, а техничный — владеющий техникой.'],['хищническ*','зверь|зверя|звери|зверей|рыб*|птиц*|животн*|растени*|насекомы*','после','хищный','Хищные звери и птицы, хищническое — о грабительском.'],['цельный|цельная|цельное|цельные|цельного|цельной|цельную|цельных','день|дня|дни|год|года|месяц|месяца|час|часа|вечер|неделю|недели|утро|ночь|лето','после','целый','Целый день, а цельный — монолитный.'],['человечн*','фактор*|ресурс*|организм*|капитал*|тело|мозг*|ошибк*','после','человеческий','Человеческий фактор, а человечный — гуманный.'],['экономика|экономики|экономику|экономикой','времени|денег|топлива|бензина|электроэнергии|места|трафика','после','экономия','Экономия времени и денег: сбережение, а экономика — хозяйство.'],['экономия|экономии|экономию|экономией','рыночн*|мирова*|цифров*|теневая|теневой','до','экономика','Рыночная и мировая экономика — хозяйство, а не сбережение.'],['экономный|экономная|экономное|экономные|экономного|экономной|экономную|экономных|экономический|экономическая|экономическое|экономические','автомобил*|двигател*|лампочк*|мотор*|прибор*|холодильник*','после','экономичный','Экономичный двигатель расходует мало, экономный — о человеке.'],['эстетичный|эстетичная|эстетичное|эстетичные|эстетичного|эстетичной|эстетичную|эстетичных|эстетичным|эстетичными','воспитани*|вкус*|наслаждени*|чувств*|удовольстви*|идеал*|восприяти*|категори*|критери*|потребност*','после','эстетический','Эстетическое воспитание и вкус, эстетичный — красивый.'],['этичный|этичная|этичное|этичные|этичного|этичной|этичную|этичных|этичным|этичными','кодекс*|комитет*|нормы|вопрос*|проблем*|аспект*|дилемм*','после','этический','Этический кодекс и комитет, этичный — соответствующий морали.'],['этический|этическая|этическое|этические|этического|этической|этическую|этических','хакер*|хакинг|майнинг|мода|моды|одежд*|косметик*','после','этичный','Этичный хакер и мода: поведение по совести.'],['эффективный|эффективная|эффективное|эффективные|эффективного|эффективной|эффективную|эффективных|эффективным|эффективными','внешность|наряд*|платье|платья|жест*|появлени*','после','эффектный','Эффектный — производящий впечатление, эффективный — результативный.'],['эффектн*','метод*|лечени*|терапи*|управлени*|менеджмент*|продвижени*','после','эффективный','Результативный метод — эффективный, эффектный — яркий.'],['принять|принима*|принял*|набрать|набира*|наполнить|наполня*','ванную','после','ванну','Ванну принимают, а ванная — это комната.'],['ванна','комната|комнаты|комнату|комнате|комнатой','после','ванная','Комнату называют ванной, а не ванна.'],['командированн*','расход*|затрат*|суточные|удостоверени*|выплат*|издержк*','после','командировочный','Командировочные расходы, а командированный — человек в командировке.'],['дипломатичный|дипломатичная|дипломатичное|дипломатичные|дипломатичного|дипломатичной|дипломатичную|дипломатичных','визит*|корпус*|паспорт*|миссия|миссии|скандал*|иммунитет*|почт*|представительств*|протокол*','после','дипломатический','Дипломатический корпус и визит, дипломатичный — тактичный.'],['демократический|демократическая|демократическое|демократические|демократического|демократической|демократическую|демократических','цены|цену|цен|ценах|ценам|стоимость|тарифы|ценник*|меню|кафе|ресторан*','после','демократичный','Демократичные цены — доступные, демократический — о строе.'],['искусственный|искусственная|искусственное|искусственные|искусственного|искусственной|искусственную|искусственных','мастер*|ремесленник*|хирург*|исполнени*','после','искусный','Искусный мастер — умелый, искусственный — ненастоящий.'],['благотворительн*','влияни*|воздействи*|климат*|почв*','после','благотворный','Благотворное влияние: полезное, благотворительный — помогающий нуждающимся.'],['благотворный|благотворная|благотворное|благотворные|благотворного|благотворной|благотворную|благотворных','фонд*|концерт*|акци*|аукцион*|ярмарк*|мероприяти*|организаци*|помощь|помощи','после','благотворительный','Благотворительный фонд помогает, благотворный — полезный.'],['компания|компании|компанию|компанией|компаний|компаниях','предвыборн*|избирательн*|посевн*','до','кампания','Кампания — серия действий; компания — фирма или общество.'],['органичный|органичная|органичное|органичные|органичного|органичной|органичную|органичных|органичным|органичными','косметик*|продукт*|удобрени*|земледели*|отход*|вещества|веществ|хлопок|кофе|чай|овощи|ферм*','после','органический','Органические продукты и удобрения, органичный — естественный.'],['продуктивный|продуктивная|продуктивное|продуктивные|продуктивного|продуктивной|продуктивную|продуктивных','магазин*|набор*|рынок|рынка|рынке|корзин*|товар*|склад*|лавк*|ларёк|палатк*','после','продуктовый','Продуктовый магазин, а продуктивный — плодотворный.'],['представительный|представительная|представительное|представительные|представительных|представительного','расход*|затрат*|подарк*','после','представительский','Представительские расходы на приём гостей.'],['представительск*','вид|вида|внешность|внешности|облик|осанка|мужчина|мужчин*','после','представительный','Представительный — внушительный видом человек.']]},
46
47
  {id:'pleonasm-dependent',category:'pleonasm',message:'Зависимое слово повторяет то, что уже сказано главным существительным.',fix:'Оставить только главное существительное.',words:['ареал* обитания','коллег* по работе','счет* на оплату','прейскурант цен','прейскуранта цен','период* времени','промежутк* времени','январь месяц','февраль месяц','март месяц','апрель месяц','май месяц','июнь месяц','июль месяц','август месяц','сентябрь месяц','октябрь месяц','ноябрь месяц','декабрь месяц']},
47
48
  {id:'date-range-period',category:'pleonasm',message:'Слово период лишнее: диапазон дат сам задаёт период.',fix:'Писать просто: с … по ….',words:['в период с','на протяжении периода','в течение периода']},
48
49
  // Structure of the page
@@ -66,14 +67,15 @@ export const RULES=[
66
67
  {id:'scientific-terms',category:'bureaucratese',message:'Научный термин в бытовом тексте звучит тяжело и отдаляет читателя.',fix:'Заменить простым словом или описать, что произошло.',words:['депресс*','апатия','апатии','апатию','мотивац*','рефлекси*','катарсис*','парадигм*','дискурс*','когнитивн*','подсознан*','подсознательн*','фоби*']},
67
68
  {id:'definition-patterns',category:'syntax',message:'Определение встроено в фразу и тяжело читается.',fix:'Вынести определение в отдельное предложение: термин — это …',words:['понимается','понимаются','понимать под']},
68
69
  // Added from the second pass over public advice: borrowings, register, stamps, claims, letter formulas
69
- {id:'borrowing-business',category:'borrowing',message:'Заимствование из деловой болтовни там, где есть обычное русское слово.',fix:'Назвать по-русски: отзыв, пример, история, сайт, помощь.',words:['сторителлинг','стаф','суппорт','support','фидбек','фидбэк','кейс*','визуал','гивэвэй','ингейджмент','контент-план','личный бренд','веб-сайт','интернет-сайт','онлайн-платформ*']},
70
- {id:'slang-register',category:'slang',message:'Жаргон звучит как переписка в чате и подходит не каждому читателю.',fix:'Сказать обычным словом: плохо, деньги, хвастаться, ерунда.',words:['бро','чел','йоу','кринж','флексить','байтить','тупо','тупить','бабло','понты','понтоваться','кидалово','подстава','ща','ежели','окей','вау','ура-ура','бухать','жрать','херачить','фигня','фигни','фигню']},
70
+ {id:'borrowing-business',category:'borrowing',message:'Заимствование из деловой болтовни там, где есть обычное русское слово.',fix:'Назвать по-русски: отзыв, пример, история, сайт, помощь.',swap:{'сторителлинг':'истории, рассказ','стаф':'вещи, персонал','суппорт':'поддержка',support:'поддержка','фидбек':'отзыв, отклик, обратная связь','фидбэк':'отзыв, отклик, обратная связь','кейс':'пример, случай, проект','визуал':'оформление, изображение, картинки','гивэвэй':'розыгрыш','ингейджмент':'вовлечённость, отклик','личный бренд':'репутация, имя','веб-сайт':'сайт','интернет-сайт':'сайт','онлайн-платформ':'платформа, сайт, сервис','экспириенс':'опыт','дедлайн':'крайний срок, срок сдачи, срок','апрув':'согласование, одобрение, подтверждение','асап':'как можно скорее, срочно','кипиай':'ключевые показатели, показатели эффективности','ивент':'мероприятие, событие','тимлид':'руководитель группы, старший в команде','тиммейт':'товарищ по команде, коллега','оффер':'предложение','рекрутер':'специалист по подбору персонала, кадровик','эйчар':'кадровик, специалист по персоналу','воркшоп':'мастер-класс, практикум','драфт':'черновик','месседж':'сообщение, главная мысль','эстимейт':'оценка стоимости, смета','роадмап':'дорожная карта, план развития','косты':'затраты, расходы','хард скилл*':'профессиональные навыки','софт скилл*':'гибкие навыки, личные качества','бэкграунд':'опыт, прошлое, происхождение','рисерч':'исследование','девайс':'устройство','апдейт':'обновление','таргет':'целевая реклама, цель','промо':'реклама, акция','спич':'речь, выступление','спичи':'речь, выступление','спича':'речь, выступление','спичем':'речь, выступление','дисконт':'скидка','паттерн':'шаблон, образец','чек-ап':'обследование, проверка','инсайт':'озарение, догадка','продакшн':'производство','челлендж':'испытание, вызов','гайд':'руководство, инструкция','вин-вин':'взаимовыгодный','диджитал':'цифровой','фича':'функция, возможность','фичи':'функции, возможности','баг':'ошибка','багфикс':'исправление ошибки','скрин':'снимок экрана','скриншот':'снимок экрана','дашборд':'панель показателей','анбоксинг':'распаковка','сейл':'распродажа, скидки','абьюз':'насилие, злоупотребление','буллинг':'травля','мейнстрим':'основное направление, массовое','коннект':'связь, контакт','тизер':'анонс','комьюнити':'сообщество','коммьюнити':'сообщество','ланч':'обед','вишлист':'список желаний','онбординг':'вхождение в должность, адаптация новичка','синк':'согласование, короткая встреча','синка':'согласование, короткая встреча','синки':'согласование, короткая встреча','синке':'согласование, короткая встреча','синком':'согласование, короткая встреча','синков':'согласование, короткая встреча','колл':'созвон, звонок','митап':'встреча, неформальная встреча специалистов','бэклог':'список задач, накопившиеся задачи','тикет':'заявка, обращение','скоуп':'объём работ, круг задач','ревью':'проверка, разбор','стейкхолдер':'заинтересованная сторона','таймлайн':'график, сроки, хронология','чекпоинт':'контрольная точка','фоллоу-ап':'повторный контакт, напоминание','хедхантинг':'переманивание специалистов, поиск руководителей','хедхантер':'охотник за кадрами','брейншторм':'мозговой штурм','брейнсторм':'мозговой штурм','воркфлоу':'порядок работы, рабочий процесс','лидген':'привлечение клиентов, поиск заказчиков','апсейл':'допродажа','релокац':'переезд, перевод в другой город','бенефит':'выгода, льгота, преимущество','ремоут':'удалённая работа, удалёнка','прокрастинац':'откладывание дел, волокита','прокрастинир':'откладывать, тянуть','реферал':'приглашённый пользователь','рефералк':'приглашение друга, бонус за приглашённого','экшен-план':'план действий','хайринг':'найм','оффбординг':'увольнение, прощание с сотрудником','вейтлист':'лист ожидания'},hints:{'контент-план':'Сказать по-русски: план публикаций.'},words:['сторителлинг','стаф','суппорт','support','фидбек','фидбэк','кейс*','визуал','гивэвэй','ингейджмент','контент-план','личный бренд','веб-сайт','интернет-сайт','онлайн-платформ*','экспириенс*','дедлайн*','апрув*','асап','кипиай','ивент*','тимлид*','тиммейт*','оффер*','рекрутер*','эйчар*','воркшоп*','драфт*','месседж*','эстимейт*','роадмап*','косты','хард скилл*','софт скилл*','бэкграунд*','рисерч*','девайс*','апдейт*','таргет*','промо','спич','спичи','спича','спичем','дисконт','дисконта','дисконту','дисконтом','дисконте','дисконты','дисконтов','паттерн*','чек-ап*','инсайт*','продакшн*','челлендж*','гайд','гайда','гайду','гайдом','гайде','гайды','гайдов','гайдам','гайдами','гайдах','вин-вин','диджитал*','фича','фичи','баг','багфикс','скрин','скриншот*','дашборд*','анбоксинг*','сейл','абьюз*','буллинг*','мейнстрим*','коннект','коннекта','коннекту','коннектом','коннекте','тизер*','комьюнити','коммьюнити','ланч*','вишлист*','онбординг*','синк','синка','синки','синке','синком','синков','колл','митап*','бэклог*','тикет*','скоуп*','ревью','стейкхолдер*','таймлайн*','чекпоинт*','фоллоу-ап*','хедхантинг*','хедхантер*','брейншторм*','брейнсторм*','воркфлоу','лидген*','апсейл*','релокац*','бенефит*','ремоут','прокрастинац*','прокрастинир*','реферал*','рефералк*','экшен-план*','хайринг*','оффбординг*','вейтлист']},
71
+ {id:'slang-register',category:'slang',message:'Жаргон звучит как переписка в чате и подходит не каждому читателю.',fix:'Сказать обычным словом: плохо, деньги, хвастаться, ерунда.',swap:{'бро':'друг, брат','чел':'человек','йоу':'привет','кринж':'стыд, неловкость','флексить':'хвастаться, красоваться','байтить':'провоцировать, подражать','тупо':'просто','тупить':'медлить, ошибаться','бабло':'деньги','понты':'хвастовство, показуха','понтоваться':'хвастаться','кидалово':'обман','подстава':'подвох, ловушка','ща':'сейчас','ежели':'если','окей':'хорошо, ладно','бухать':'пить, выпивать','жрать':'есть','херачить':'работать, делать','фигн':'ерунда','юзать':'использовать, пользоваться','юзер':'пользователь','расшарить':'поделиться','фиксить':'исправлять, чинить','чекать':'проверять','пушить':'продвигать, подталкивать','форсить':'продвигать, навязывать','скипнуть':'пропустить','профит':'прибыль, выгода','мастхэв':'обязательная вещь','лайфхак':'совет, приём, хитрость','хайп':'шумиха, ажиотаж','фейк':'обман, подделка, фальшивка','треш':'мусор, ерунда, бардак','треша':'мусор, ерунда, бардак','трешем':'мусор, ерунда, бардак','трешовый':'мусор, ерунда, бардак','трешовая':'мусор, ерунда, бардак','трешовое':'мусор, ерунда, бардак','трешовые':'мусор, ерунда, бардак','плиз':'пожалуйста','респект':'уважение','респекта':'уважение','респектом':'уважение','позер':'показушник, выпендрежник','лузер':'неудачник','хейт':'ненависть, неприязнь','ноунейм':'малоизвестный, безымянный','рандом':'случайность','топовый':'лучший, главный','коллаб':'совместный проект','коллаба':'совместный проект','коллабу':'совместный проект','коллабы':'совместный проект','коллабе':'совместный проект','коллабом':'совместный проект','коллаборация':'совместный проект','коллаборации':'совместный проект','коллаборацию':'совместный проект','коллаборацией':'совместный проект','пранк':'розыгрыш','сквад':'отряд, команда','пати':'вечеринка','ачивка':'достижение','изи':'легко, просто','найс':'отлично, хорошо','факап':'провал, ошибка','скам':'мошенничество','ассайн':'назначить, поручить','зашарить':'поделиться, показать','пингануть':'напомнить, написать, окликнуть','синкнуться':'согласоваться, созвониться, сверить','таск':'задача','таска':'задача','таски':'задачи','чекнуть':'проверить, посмотреть','тулза':'инструмент, программа','тулзы':'инструменты, программы','апрувнуть':'одобрить, согласовать'},hints:{'вау':'Убрать или назвать, что удивило.','ура-ура':'Убрать восклицание и назвать повод.'},words:['бро','чел','йоу','кринж','флексить','байтить','тупо','тупить','бабло','понты','понтоваться','кидалово','подстава','ща','ежели','окей','вау','ура-ура','бухать','жрать','херачить','фигня','фигни','фигню','юзать','юзер*','расшарить','фиксить','чекать','пушить','форсить','скипнуть','профит','мастхэв','лайфхак*','хайп*','фейк*','треш','треша','трешем','трешовый','трешовая','трешовое','трешовые','плиз','респект','респекта','респектом','позер*','лузер*','хейт*','ноунейм*','рандом*','топовый','коллаб','коллаба','коллабу','коллабы','коллабе','коллабом','коллаборация','коллаборации','коллаборацию','коллаборацией','пранк*','сквад*','пати','ачивка','изи','найс','факап*','скам','ассайн*','зашарить','пингануть','синкнуться','таск','таска','таски','чекнуть','тулза','тулзы','апрувнуть']},
71
72
  {id:'stamp-clickbait',category:'stamp',message:'Интрига вместо содержания: читателя заманивают, а не называют тему.',fix:'Назвать результат или факт прямо в первых словах.',words:['вы удивитесь','вы не поверите','прочитайте эту статью','узнайте как','узнай как','вот что мы узнали','не переключайтесь','секрет успеха','секреты успеха','незаметно убива*','доверьтесь мне','доверьтесь профессионалам']},
72
73
  {id:'stamp-call-to-action',category:'stamp',message:'Призыв действовать прямо сейчас давит на читателя и ничего не доказывает.',fix:'Назвать причину или выгоду, а решение оставить читателю.',words:['оставьте заявку','звоните прямо сейчас','бронируйте сегодня','бронируйте сейчас','купи скорее','ставь лайк','подпишись','подпишитесь сейчас','перешлите друзьям','предупредите всех','обращайтесь']},
73
74
  {id:'stamp-idioms-more',category:'stamp',message:'Готовый образ из газет и рекламы: читатель пролистывает его, не вдумываясь.',fix:'Сказать то же прямо, своими словами и с конкретным фактом.',words:['бытует мнение','в эпоху цифровых технологий','братья наши меньшие','столбик термометра','просторы интернета','глобальная сеть','всемирная паутина','в сети интернет','мировые сети','оружие массового поражения','делом всей жизни','нож в спину','сломано немало копий','на войне как на войне','как грибы после дождя','британские ученые','верхушка айсберга','конкуренты не дремлют','метать бисер','рано или поздно','от а до я','залог успеха','залог успешных','стремительно набира* популярность','повальное увлечение','яркий тренд','незаменимый помощник','изо всех сил','идеальное сочетание','мурашки по коже','до мурашек','покрывается мурашками','нотки','нотка','нотками','насыщенный букет','богатая палитра','бомба замедленного действия','ворваться в чарты','искорки счастья','искорки шампанского','искорки волшебства','волшебное время','зимняя сказка','новогодняя сказка','время волшебства и чудес','играл* ключев* рол*','сыграл* ключев* рол*','играл* важн* рол*','сыграл* важн* рол*']},
74
75
  {id:'stamp-business-claims',category:'stamp',message:'Фраза из коммерческих текстов: звучит одинаково у всех и ничего не доказывает.',fix:'Заменить фактом: сколько, за какой срок, для кого, с какой гарантией.',words:['полный спектр услуг','полный цикл услуг','полный комплекс услуг','решать задачи бизнеса','решаем задачи бизнеса','задачи бизнеса','в кратчайшие сроки','кратчайшие сроки','точно в срок','высочайшего качества','высокое качество','наилучшее качество','высокий профессионализм','высочайший профессионализм','профессионалы своего дела','профессионал своего дела','обратитесь к профессионалам','квалифицированный персонал','квалифицированные специалисты','опытные специалисты','один из лидеров','один из ведущих','одна из ведущих','один из крупнейших','одна из крупнейших','признанный лидер','ведущий специалист','ведущие специалисты','лучший на рынке','работает на рынке','работаем на рынке','особое внимание','высокие темпы роста','передовые технологии','прорывные технологии','инновационные решения','конкурентные преимущества','быстро и качественно','лучшие цены','самые низкие цены','самые выгодные условия','выгодные условия','на самом высоком уровне','на высоком профессиональном уровне','на мощной технологической базе','завоевали доверие','честная конкуренция','корпоративная философия']},
75
76
  {id:'stamp-formal-letter',category:'stamp',message:'Формула из официальных писем: вежливость по шаблону, а не живая речь.',fix:'Написать по-человечески и коротко или убрать формулу.',words:['ваш звонок очень важен для нас','приносим свои глубочайшие извинения','приносим глубочайшие извинения','глубочайшие извинения','искренне ваш','заранее спасибо','всего наилучшего','спасибо за понимание','спасибо за внимательное отношение','буду рад ответить на ваши вопросы','буду рад сотрудничеству','с радостью предложим','именно поэтому мы','доброго времени суток','с наступающим','желаю профессиональных успехов','здоровья и успехов','здоровья и процветания','вынуждены сообщить']},
76
- {id:'bureaucratese-phrases',category:'bureaucratese',message:'Канцелярская формула: звучит как документ, а не как человек.',fix:'Сказать проще: деньги, программы, если есть, поэтому.',words:['денежные средства','денежных средств','материальная помощь','материальной помощи','программное обеспечение','программного обеспечения','товары и услуги','товары или услуги','товаров и услуг','техническая возможность','технической возможности','при наличии','в этой связи','обращаем ваше внимание','обращаю ваше внимание','хочу обратить ваше внимание','сообщаем вам','множество нюансов','выработаем стратегию','выработали стратегию']},
77
+ {id:'bureaucratese-phrases',category:'bureaucratese',message:'Канцелярская формула: звучит как документ, а не как человек.',fix:'Сказать проще: деньги, программы, если есть, поэтому.',exact:true,swap:{'денежные средства':'деньги','денежных средств':'денег','программное обеспечение':'программы','программного обеспечения':'программ','техническая возможность':'возможность','технической возможности':'возможности','при наличии':'если есть','в этой связи':'поэтому','выработаем стратегию':'составим план','выработали стратегию':'составили план'},hints:{'материальн':'Сказать прямо: деньги, вещи, продукты.','товар':'Назвать, что именно продаёте или делаете.','обраща':'Убрать и сразу написать сам факт.','хочу обратить':'Убрать и сразу написать сам факт.','сообщаем вам':'Убрать и сразу написать новость.','множество нюансов':'Назвать сами нюансы или главный из них.'},words:['денежные средства','денежных средств','материальная помощь','материальной помощи','программное обеспечение','программного обеспечения','товары и услуги','товары или услуги','товаров и услуг','техническая возможность','технической возможности','при наличии','в этой связи','обращаем ваше внимание','обращаю ваше внимание','хочу обратить ваше внимание','сообщаем вам','множество нюансов','выработаем стратегию','выработали стратегию']},
78
+ {id:'bureaucratese-turns',category:'bureaucratese',message:'Канцелярский оборот длиннее простого слова и звучит как документ.',fix:'Заменить простым словом или убрать.',exact:true,swap:{'в случае если':'если','в том случае если':'если','при условии что':'если','за исключением':'кроме','в соответствии с':'по, согласно','в связи с тем что':'потому что','в силу того что':'потому что','ввиду того что':'потому что','по причине того что':'потому что','вследствие того что':'потому что','вследствие':'из-за','по причине':'из-за','в отношении':'о, про, к','касательно':'о, про','по вопросу':'о','в ходе':'во время, при','в процессе':'при, во время','для того чтобы':'чтобы','с учетом того что':'учитывая','по мере необходимости':'когда нужно','в случае необходимости':'при необходимости, если нужно','на безвозмездной основе':'бесплатно','на платной основе':'за плату','на договорной основе':'по договору','на постоянной основе':'постоянно','на регулярной основе':'регулярно','на ежедневной основе':'ежедневно','на индивидуальной основе':'индивидуально','на круглосуточной основе':'круглосуточно','на добровольной основе':'добровольно','на долгосрочной основе':'надолго','в срочном порядке':'срочно','в оперативном порядке':'быстро','в индивидуальном порядке':'индивидуально','в установленном порядке':'по правилам','в установленные сроки':'в срок','в короткие сроки':'быстро','в сжатые сроки':'быстро','в максимально короткие сроки':'быстро','в минимальные сроки':'быстро','в полном объеме':'полностью','надлежащим образом':'правильно','по факту':'после, когда, фактически','ознакомьтесь':'посмотрите, прочитайте','вносить оплату':'оплатить','принять к сведению':'учесть','настоящим сообщаем':'сообщаем','иметь возможность':'мочь','имеет возможность':'может'},hints:{'представля* собой':'Заменить: это или тире.','надлежащ*':'Сказать проще: правильный, нужный.','приобре*':'Сказать проще: купить, получить.','реализ* продукци*':'Сказать проще: продавать.','реализац* товар*':'Сказать проще: продажа товаров.','производ* оплат*':'Заменить глаголом: оплатить.','производ* замер*':'Сказать проще: замерять.','производ* расчет*':'Сказать проще: считать, рассчитать.','оказыва* содействие':'Сказать проще: помогать.','оказыва* влияние':'Сказать проще: влиять.','предоставля* возможность':'Сказать проще: позволять, дать возможность.','предоставля* скидк*':'Сказать проще: давать скидку.','довод* до сведения':'Сказать проще: сообщать.','информиру* вас':'Сказать проще: сообщаем.','ввод* в эксплуатацию':'Сказать проще: запуск.','имеет* в наличии':'Сказать проще: есть, в наличии.','имеют* в наличии':'Сказать проще: есть, в наличии.'},words:['в случае если','в том случае если','при условии что','за исключением','в соответствии с','в связи с тем что','в силу того что','ввиду того что','по причине того что','вследствие того что','вследствие','по причине','в отношении','касательно','по вопросу','в ходе','в процессе','для того чтобы','с учетом того что','по мере необходимости','в случае необходимости','на безвозмездной основе','на платной основе','на договорной основе','на постоянной основе','на регулярной основе','на ежедневной основе','на индивидуальной основе','на круглосуточной основе','на добровольной основе','на долгосрочной основе','в срочном порядке','в оперативном порядке','в индивидуальном порядке','в установленном порядке','в установленные сроки','в короткие сроки','в сжатые сроки','в максимально короткие сроки','в минимальные сроки','в полном объеме','представля* собой','надлежащ*','надлежащим образом','по факту','ознакомьтесь','приобре*','реализ* продукци*','реализац* товар*','производ* оплат*','производ* замер*','производ* расчет*','вносить оплату','оказыва* содействие','оказыва* влияние','предоставля* возможность','предоставля* скидк*','довод* до сведения','принять к сведению','информиру* вас','настоящим сообщаем','ввод* в эксплуатацию','иметь возможность','имеет возможность','имеет* в наличии','имеют* в наличии']},
77
79
  {id:'pleonasm-more',category:'pleonasm',message:'Слово дублирует смысл соседнего или тянет текст.',fix:'Оставить одно слово, которое несёт смысл.',words:['наглядные примеры','наглядный пример','наиболее оптимальн*','самый оптимальн*','в удобное для вас время','в любое удобное время','провайдер услуг']},
78
80
  {id:'generalization-any',category:'generalization',message:'Слова все и любой обещают больше, чем можно проверить.',fix:'Назвать конкретные случаи или количество.',words:['в любой ситуации','для любой ситуации','в любой точке','на любую тему','любую вещь','всех типов','все виды','всегда есть выбор','каждый сам решает','предусмотрим все']},
79
81
  {id:'vague-science-appeal',category:'vague',message:'Ссылка на безымянные исследования не даёт проверить сказанное.',fix:'Назвать источник, год и цифру или написать, что наблюдал сам.',words:['ученые доказали','исследования показали','согласно недавним исследованиям','согласно исследованиям','по последним данным','по данным исследований','как показывают исследования','как показывает статистика']},
@@ -84,16 +86,16 @@ export const RULES=[
84
86
  {id:'modality-promise',category:'modality',message:'Обещание читателю в форме вы станете звучит как реклама курса.',fix:'Описать, что есть в тексте, а выводы оставить читателю.',words:['вы научитесь','вы узнаете','вы станете','вы получите','вы найдете','вам потребуется']},
85
87
  {id:'filler-start',category:'filler',start:true,message:'Слово-связка в начале даёт паузу вместо мысли.',fix:'Начать сразу с мысли.',words:['кстати','в общем','по сути','да и вообще','сразу скажу']},
86
88
  {id:'filler-phrases',category:'filler',message:'Пустая фраза занимает место и ничего не сообщает.',fix:'Убрать или заменить одним конкретным примером.',words:['бла-бла-бла','пятое-десятое','и далее в таком духе','без преувеличения','поверьте мне','правда-правда']},
87
- {id:'pronoun-ours',category:'pronoun',message:'Слово наш в рекламном смысле выдаёт самохвальство и сужает текст.',fix:'Назвать предмет прямо: услуги, клиенты, сайт.',words:['наши услуги','наши клиенты','наши офисы','о нашей компании','на нашем интернет-сайте','на нашем сайте']},
89
+ {id:'pronoun-ours',category:'pronoun',message:'Слово наш в рекламном смысле выдаёт самохвальство и сужает текст.',fix:'Назвать предмет прямо: услуги, клиенты, сайт.',exact:true,swap:{'наши услуги':'услуги','наши клиенты':'клиенты','наши офисы':'офисы','о нашей компании':'о компании','на нашем интернет-сайте':'на сайте','на нашем сайте':'на сайте'},words:['наши услуги','наши клиенты','наши офисы','о нашей компании','на нашем интернет-сайте','на нашем сайте']},
88
90
  {id:'euphemism-soft',category:'euphemism',message:'Мягкое слово прячет суть дела.',fix:'Назвать, что именно случилось и по чьей вине.',words:['форс-мажорн*','форс-мажор','политес']},
89
91
  {id:'preachy-refusal',category:'preachy',message:'Список тех, с кем автор не работает, звучит как ультиматум.',fix:'Написать, с кем работаешь и чем это удобно обеим сторонам.',words:['не работаю с','не работаем с','в обязательном порядке']},
90
92
  {id:'self-praise',category:'evaluation',message:'Похвала себе без доказательства читается как самореклама.',fix:'Показать результат: проект, срок, отзыв.',words:['мы не новички','нам можно доверить','мы серьезная фирма','никогда не срываю сроки','мы делаем лучше всех']},
91
93
  // Common spelling and grammar mistakes, and colloquial forms
92
- {id:'mistake-spelling',category:'mistake',message:'Частая ошибка: слово пишется слитно, раздельно или через дефис иначе.',fix:'Правильно: в общем, в принципе, вкратце, из-за, из-под, как будто, впоследствии, наподобие, то есть, в смысле.',words:['вообщем','вобщем','впринципе','в кратце','из за','из под','как-будто','в последствии','на подобии','тоесть','всмысле']},
94
+ {id:'mistake-spelling',category:'mistake',message:'Частая ошибка: слово пишется слитно, раздельно или через дефис иначе.',fix:'Правильно: в общем, в принципе, вкратце, из-за, из-под, как будто, впоследствии, наподобие, то есть, в смысле.',exact:true,swap:{'вообщем':'в общем','вобщем':'в общем','впринципе':'в принципе','в кратце':'вкратце','из за':'из-за','из под':'из-под','как-будто':'как будто','в последствии':'впоследствии','на подобии':'наподобие','тоесть':'то есть','всмысле':'в смысле'},words:['вообщем','вобщем','впринципе','в кратце','из за','из под','как-будто','в последствии','на подобии','тоесть','всмысле']},
93
95
  {id:'mistake-po-case',category:'mistake',message:'Предлог по в значении после чего требует предложного падежа.',fix:'Писать по приезде, по прибытии, по окончании, по завершении, по истечении, по возвращении.',words:['по приезду','по прибытию','по окончанию','по завершению','по истечению','по возвращению']},
94
96
  {id:'mistake-soglasno',category:'mistake',message:'После согласно нужен дательный падеж, а не родительный.',fix:'Писать согласно договору, приказу, графику, плану, закону, решению, правилам.',words:['согласно договора','согласно приказа','согласно графика','согласно плана','согласно закона','согласно решения','согласно распоряжения','согласно требований','согласно условий','согласно правил']},
95
97
  {id:'mistake-oplata',category:'mistake',message:'Оплачивают что-то, а платят за что-то: оплатить за услугу смешивает два оборота.',fix:'Писать оплатить работу или заплатить за работу. Оплатить за меня или за месяц вперёд — верно.',words:['оплатить за','оплатил за','оплатила за','оплатили за','оплачу за','оплатите за','оплачивать за','оплачиваем за','оплачивают за']},
96
98
  {id:'mistake-oboih',category:'mistake',message:'Со словами женского рода пишут обеих, обеим, обеими.',fix:'Писать обеих сторон, обеим сторонам, обеими руками.',words:['обоих сторон','обоим сторонам','обоими сторонами','обоих рук','обоими руками']},
97
99
  {id:'mistake-context',category:'mistake',message:'Если речь о времени или причине, пишется иначе: в течение, в продолжение, вследствие, несмотря на.',fix:'Оставить как есть, только если речь о течении реки, продолжении книги, следствии по делу или о том, кто куда смотрит.',words:['в течении','в продолжении','в следствии','не смотря на']},
98
- {id:'colloquial-forms',category:'colloquial',message:'Просторечная форма: в письменном тексте выглядит как ошибка.',fix:'Литературно: их, класть или положить, поезжай, ездят, хотят, ляг, сейчас, его.',words:['ихн*','ложить','ложит','ложишь','ложат','ложим','ложите','ложил','ложила','ложили','ехай','ехайте','едь','едьте','ездию','ездиют','хочут','ляжь','ляжьте','победю','щас','евонн*','евоный','евоная']},
100
+ {id:'colloquial-forms',category:'colloquial',message:'Просторечная форма: в письменном тексте выглядит как ошибка.',fix:'Литературно: их, класть или положить, поезжай, ездят, хотят, ляг, сейчас, его.',exact:true,swap:{'ихн':'их','ложить':'класть','ложит':'кладёт','ложишь':'кладёшь','ложат':'кладут','ложим':'кладём','ложите':'кладите, кладёте','ложил':'клал','ложила':'клала','ложили':'клали','ехай':'поезжай, езжай','ехайте':'поезжайте','едь':'поезжай','едьте':'поезжайте','ездию':'езжу','ездиют':'ездят','хочут':'хотят','ляжь':'ляг','ляжьте':'лягте','победю':'выиграю, одержу победу','щас':'сейчас','евонн':'его','евоный':'его','евоная':'его'},words:['ихн*','ложить','ложит','ложишь','ложат','ложим','ложите','ложил','ложила','ложили','ехай','ехайте','едь','едьте','ездию','ездиют','хочут','ляжь','ляжьте','победю','щас','евонн*','евоный','евоная']},
99
101
  ];
package/src/style.js CHANGED
@@ -6,9 +6,40 @@ const esc=s=>s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
6
6
  // "осуществл*" matches any ending; spaces between words may be any whitespace.
7
7
  const tokenSource=t=>t.endsWith('*')?esc(norm(t.slice(0,-1)))+'['+L+']*':esc(norm(t));
8
8
  function compile(rule){const alts=(rule.words||[]).map(w=>w.trim().split(/\s+/).map(tokenSource).join('\\s+'));return alts.length?new RegExp('(?<!['+L+'])(?:'+alts.join('|')+')(?!['+L+'])','g'):null;}
9
- const compiled=RULES.map(rule=>({rule,re:compile(rule)}));
10
- // A rule with swap gives the replacement for the exact word found; the longest matching stem wins.
11
- const swapped=(rule,word)=>{if(!rule.swap)return rule;const k=Object.keys(rule.swap).filter(k=>word.startsWith(k)).sort((a,b)=>b.length-a.length)[0];return k?{...rule,fix:'Заменить обычным словом: '+rule.swap[k]+'.'}:rule;};
9
+ // Paronyms: [forms, neighbours, where the neighbour stands (после, до, любой), right word, note]. Only the wrong
10
+ // word is marked, and only next to a neighbour that makes the mistake certain: «одеть пальто», not «одеть ребёнка».
11
+ const alt=list=>'(?:'+list.split('|').map(w=>w.trim().split(/\s+/).map(tokenSource).join('\\s+')).join('|')+')';
12
+ function pairRule(rule,[forms,near,where,right,note]){
13
+ const f='(?<!['+L+'])'+alt(forms)+'(?!['+L+'])',n=alt(near),after=f+'(?=\\s+'+n+'(?!['+L+']))',before='(?<=(?<!['+L+'])'+n+'\\s+)'+f;
14
+ const words=/^[а-яё]+(?:,\s*[а-яё]+)*$/i.test(right)?right.split(/,\s*/):null;
15
+ return {rule:{...rule,pairs:undefined,fix:'Правильно: '+right+'. '+note,...(words&&{variants:words})},re:new RegExp(where==='до'?before:where==='любой'?after+'|'+before:after,'g')};
16
+ }
17
+ const compiled=RULES.flatMap(rule=>rule.pairs?rule.pairs.map(p=>pairRule(rule,p)):[{rule,re:compile(rule)}]);
18
+ // A rule with swap gives the replacement for the exact word found; the longest matching key wins. A key is a stem
19
+ // («релевантн») or a pattern like the rule's words («памятн* сувенир*»).
20
+ // hints hold advice for words with no drop-in replacement («в рамках проекта» is not «в проекта»).
21
+ // replace lists the variants a click can insert: one-word ones (the server puts them in the found word's form),
22
+ // or every variant when the rule is exact (a fixed form, nothing to inflect). Nothing changes without a click.
23
+ // $1, $2 in a variant stand for the words found, as written: «памятные сувениры» → «$2» → «сувениры».
24
+ const keyRe=new Map();
25
+ const fits=(k,word)=>{if(!/[*\s]/.test(k))return word.startsWith(k);let re=keyRe.get(k);if(!re)keyRe.set(k,re=new RegExp('^'+k.split(/\s+/).map(tokenSource).join('\\s+')+'(?!['+L+'])'));return re.test(word);};
26
+ const longest=(map,word)=>map&&Object.keys(map).filter(k=>fits(k,word)).sort((a,b)=>b.length-a.length)[0];
27
+ const swapped=(rule,word,found=word)=>{
28
+ const k=longest(rule.swap,word),h=longest(rule.hints,word);
29
+ if(h&&(!k||h.length>k.length))return {...rule,fix:rule.hints[h]};
30
+ if(!k)return rule;
31
+ const parts=found.split(/\s+/),all=rule.swap[k].split(/,\s*/).filter(Boolean).map(w=>w.replace(/\$(\d)/g,(_,n)=>parts[n-1]||''));
32
+ const fix=(rule.exact?'Заменить: ':'Заменить обычным словом: ')+all.join(', ')+'.';
33
+ if(rule.exact)return {...rule,fix,replace:[...new Set(all)],exact:true};
34
+ const variants=[...new Set(all.filter(w=>!/\s/.test(w)))];
35
+ return {...rule,fix,...(variants.length&&{variants,key:k})};
36
+ };
37
+ // variants are dictionary forms. The server puts them in the found word's form; without it a click inserts them as
38
+ // written, so they become buttons only where the found word is a dictionary form of the same kind too:
39
+ // «осуществлять → делать», but not «осуществляет → делать».
40
+ const INF=/(?:ть|ти|чь)(?:ся|сь)?$/,ADJ=/(?:ый|ий|ой)$/,NOUN=/(?:ия|ие|ость)$/;
41
+ const asIs=(found,key,v)=>{const w=norm(found).split(/[\s-]+/).pop();if(key&&w===key)return true;if(INF.test(w))return INF.test(v);if(ADJ.test(w))return ADJ.test(v);if(NOUN.test(w))return !INF.test(v)&&!ADJ.test(v);return false;};
42
+ const offered=(r,found)=>{if(!r.variants)return r;const {key,...rest}=r,replace=r.variants.filter(v=>asIs(found,key,v));return replace.length?{...rest,replace}:rest;};
12
43
  export const sentences=text=>[...text.matchAll(/[^.!?…]+(?:[.!?…]+|$)/g)].map(m=>({start:m.index,end:m.index+m[0].length,text:m[0]})).filter(s=>s.text.trim());
13
44
  const wordCount=s=>(s.match(new RegExp('['+L+'0-9]+(?:-['+L+']+)*','g'))||[]).length;
14
45
  const trimmed=(s,offset=0)=>{const lead=s.text.length-s.text.trimStart().length;return {start:s.start+offset+lead,end:s.start+offset+s.text.trimEnd().length};};
@@ -34,9 +65,20 @@ export const BUILTIN={
34
65
  'typo-dash':{category:'typography',message:'Дефис вместо тире.',fix:'Между словами — длинное тире с пробелами: «Москва — столица». Между числами — короткое без пробелов: «5–10».'},
35
66
  'typo-repeat-punct':{category:'typography',message:'Знак препинания повторяется.',fix:'Оставьте один знак. Можно только «?!», «?..», «!..» и многоточие.'},
36
67
  'typo-initials':{category:'typography',message:'Инициалы слиплись.',fix:'Разделите пробелом: «И. И. Иванов».'},
68
+ 'typo-range-dash':{category:'typography',message:'Дефис в диапазоне.',fix:'Между числами, годами, временем и днями — короткое тире без пробелов: «10:00–18:00», «пн–пт», «5–10 мм».'},
69
+ 'typo-dash-glued':{category:'typography',message:'Тире прилипло к слову с одной стороны.',fix:'Поставьте пробелы с обеих сторон: «Москва — столица».'},
70
+ 'typo-ellipsis':{category:'typography',message:'Три точки вместо многоточия.',fix:'Поставьте знак многоточия: «…».'},
71
+ 'typo-abbr-space':{category:'typography',message:'Нет пробела после сокращения.',fix:'Отделите пробелом: «г. Москва», «ул. Ленина», «см. рис. 3».'},
72
+ 'typo-abbr-inner':{category:'typography',message:'Сокращение без пробела внутри.',fix:'Пишите с пробелом: «т. е.», «и т. д.», «т. к.».'},
73
+ 'typo-year-glued':{category:'typography',message:'Год слипся со словом «год» или «г.».',fix:'Отделите пробелом: «2024 г.», «в 2024 году».'},
74
+ 'typo-currency-glued':{category:'typography',message:'Сумма слиплась со знаком валюты.',fix:'Отделите пробелом: «1500 ₽», «1500 руб.».'},
75
+ 'typo-power':{category:'typography',message:'Степень записана цифрой.',fix:'Поставьте знак степени: «м²», «м³».'},
76
+ 'typo-ordinal':{category:'typography',message:'Лишние буквы в окончании порядкового числительного.',fix:'Пишите коротко: «25-й», «1-я», «5-е», «в 3-м».'},
77
+ 'typo-number-sign':{category:'typography',message:'Знак номера вплотную к числу.',fix:'Отделите пробелом: «№ 5».'},
78
+ 'typo-copyright':{category:'typography',message:'Знак набран буквами в скобках.',fix:'Поставьте сам знак: ©, ™, ®.'},
37
79
  'typo-mixed-script':{category:'typography',message:'В слове смешаны русские и латинские буквы: так бывает после копирования. Поиск и проверка орфографии такое слово не узнают.',fix:'Наберите слово заново на одной раскладке.'},
38
80
  };
39
- // Typography: [id, pattern]; a match must not span removed markup or a joined line break (see plain below).
81
+ // Typography: [id, pattern, fix?]; fix(match) gives the text a click puts in its place. A match must not span removed markup or a joined line break (see plain below).
40
82
  const TYPO=[
41
83
  ['typo-double-space',/(?<=\S) {2,}(?=\S)/g],
42
84
  ['typo-space-before',/(?<=\S) +(?=[,;](?![-()DPp])|\)|»(?!\s|$)|[.!?](?=\s|$))|(?<=[^\s\d]) +(?=:(?=\s|$))|(?<=[(«]) +(?=\S)/g],
@@ -45,6 +87,18 @@ const TYPO=[
45
87
  ['typo-repeat-punct',/[!?]{3,}|!!|\?\?|,{2,}|;{2,}|(?<![.?!])\.\.(?!\.)|\.{4,}/g],
46
88
  // «И.И. Иванов», «Иванов И.И.», «А.Пушкин»; «Ф.И.О.» and «до Н.Э.» are abbreviations, not initials.
47
89
  ['typo-initials',/(?<!\p{L})[А-ЯЁ]\.[А-ЯЁ]\.(?=\s?[А-ЯЁ][а-яё])|(?<=[А-ЯЁ][а-яё]+\s)[А-ЯЁ]\.[А-ЯЁ]\.(?!\p{L})|(?<![\p{L}.])[А-ЯЁ]\.(?=[А-ЯЁ][а-яё]{2,})/gu],
90
+ // Ranges take an en dash: time, days of the week, years, numbers with a unit, days of a month, months, centuries.
91
+ ['typo-range-dash',new RegExp(['(?:(?<![\\d:.])\\d{1,2}[:.]\\d{2})-(?:\\d{1,2}[:.]\\d{2}(?![\\d:.]))','(?:(?<![А-Яа-яЁё])(?:[Пп]н|[Вв]т|[Сс]р|[Чч]т|[Пп]т|[Сс]б|[Вв]с))-(?:(?:[Пп]н|[Вв]т|[Сс]р|[Чч]т|[Пп]т|[Сс]б|[Вв]с)(?![А-Яа-яЁё]))','(?:(?<![\\d.\\-/])(?:1[89]|20)\\d{2})-(?:(?:1[89]|20)\\d{2}(?![\\d\\-./]))','(?:(?<![\\d.,\\-/:])\\d+(?:[.,]\\d+)?)-(?:\\d+(?:[.,]\\d+)?\\s?(?:мм|см|м|км|кг|г|т|л|мл|%|₽|руб|шт|дн|дня|дней|час|часа|часов|мин|минут|лет|года|месяц|месяца|месяцев|недел|раз|человек)(?![А-Яа-яЁё]))','(?:(?<![\\d\\-.])\\d{1,2})-(?:\\d{1,2}\\s+(?:января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)(?![А-Яа-яЁё]))','(?:(?<![А-Яа-яЁё])(?:[Яя]нварь|[Фф]евраль|[Мм]арт|[Аа]прель|[Мм]ай|[Ии]юнь|[Ии]юль|[Аа]вгуст|[Сс]ентябрь|[Оо]ктябрь|[Нн]оябрь|[Дд]екабрь))-(?:(?:январь|февраль|март|апрель|май|июнь|июль|август|сентябрь|октябрь|ноябрь|декабрь)(?![А-Яа-яЁё]))','(?:(?<![A-Za-z])[IVXL]+)-(?:[IVXL]+\\s*(?:вв?\\.|век))'].join('|'),'g'),m=>m.replace('-','–')],
92
+ ['typo-dash-glued',/(?<=[А-Яа-яЁё,]) —(?=[А-Яа-яЁё])|(?<=[А-Яа-яЁё])— (?=[А-Яа-яЁё])/g,()=>' — '],
93
+ ['typo-ellipsis',/(?<![.…])\.\.\.(?![.…])/g,()=>'…'],
94
+ ['typo-abbr-space',/(?<![А-Яа-яЁё.])(?:(?:г|ул|д|пр|пер|обл|кв|корп|стр|пос|рис|гл|им|тел)\.[А-ЯЁ0-9]|см\.[А-Яа-яЁё0-9])[А-Яа-яЁё0-9]*/g,m=>m.replace('.','. ')],
95
+ ['typo-abbr-inner',/(?<![А-Яа-яЁё.])(?:т\.е|т\.д|т\.п|т\.к|т\.н|т\.ч|н\.э)\.(?!\s?[А-Яа-яЁё]\.)/g,m=>m.replace('.','. ')],
96
+ ['typo-year-glued',/(?<!\d)(?:1[89]|20)\d{2}(?:гг?\.|год(?:а|у|ом)?(?![А-Яа-яЁё]))/g,m=>m.slice(0,4)+' '+m.slice(4)],
97
+ ['typo-currency-glued',/(?<![\d.,])\d+(?:[.,]\d+)?(?:₽|руб\.|рубл[а-яё]*|руб(?![А-Яа-яЁё]))/g,m=>m.replace(/(?=₽|руб)/,' ')],
98
+ ['typo-power',/(?<![А-Яа-яЁёA-Za-z\d])[кдсм]?м[23](?![\dА-Яа-яЁёA-Za-z])/g,m=>m.slice(0,-1)+(m.endsWith('2')?'²':'³')],
99
+ ['typo-ordinal',/(?<=\d)-(?:ый|ий|ой|ей|ая|ое|ые|ых|их|ым|ом|ую|ого|ому|ыми|ими)(?![А-Яа-яЁё])/g,m=>'-'+{ый:'й',ий:'й',ой:'й',ей:'й',ая:'я',ое:'е',ые:'е',ых:'х',их:'х',ым:'м',ом:'м',ую:'ю',ого:'го',ому:'му',ыми:'ми',ими:'ми'}[m.slice(1)]],
100
+ ['typo-number-sign',/№\d+/g,m=>'№ '+m.slice(1)],
101
+ ['typo-copyright',/\([cс]\)(?=\s*\d)|\((?:tm|TM|r|R)\)/g,m=>({'(tm)':'™','(TM)':'™','(r)':'®','(R)':'®'})[m]||'©'],
48
102
  ];
49
103
  // Mixed scripts are checked word by word: one pattern over the whole text backtracked for seconds on a long word.
50
104
  const MIXED=/\p{Script=Cyrillic}\p{Script=Latin}|\p{Script=Latin}\p{Script=Cyrillic}/u;
@@ -75,16 +129,16 @@ const NOMINAL=new RegExp('(?:(?<!['+L+'])['+L+']+(?:ние|ния|нию|ние
75
129
  const REPEAT_SKIP=new Set(['который','которая','которое','которые','которых','которым','потому','только','сейчас','больше','меньше','просто','сделать','делать','чтобы','когда','после','через','between','about']);
76
130
  // blocks: [{text, kind:'p'|'li'|'h', tag?:'H1'|'H2'…}] → issues: [{block,start,end,id,category,label,message,fix}]
77
131
  export function checkStyle(blocks,{maxSentenceWords=25,maxParagraphWords=120}={}){
78
- const issues=[],add=(block,start,end,id,rule)=>issues.push({block,start,end,id,category:rule.category,label:CATEGORIES[rule.category]||rule.category,message:rule.message,fix:rule.fix});
132
+ const issues=[],add=(block,start,end,id,rule)=>issues.push({block,start,end,id,category:rule.category,label:CATEGORIES[rule.category]||rule.category,message:rule.message,fix:rule.fix,...(rule.replace&&{replace:rule.replace}),...(rule.exact&&{exact:true}),...(rule.variants&&{variants:rule.variants})});
79
133
  blocks.forEach((b,i)=>{
80
134
  const text=b.text,low=norm(text),list=sentences(text);
81
135
  // A start-only rule needs nothing but spaces between the sentence start and the match.
82
- for(const {rule,re} of compiled){if(!re)continue;re.lastIndex=0;for(const m of low.matchAll(re)){if(rule.start){const s=sentenceAt(list,m.index);if(!s||low.slice(s.start,m.index).trim()!=='')continue;}add(i,m.index,m.index+m[0].length,rule.id,swapped(rule,m[0]));}}
136
+ for(const {rule,re} of compiled){if(!re)continue;re.lastIndex=0;for(const m of low.matchAll(re)){if(rule.start){const s=sentenceAt(list,m.index);if(!s||low.slice(s.start,m.index).trim()!=='')continue;}{const found=text.slice(m.index,m.index+m[0].length);add(i,m.index,m.index+m[0].length,rule.id,offered(swapped(rule,m[0],found),found));}}}
83
137
  // Blocks map every character to the source; gaps are spaces that stand for code, an image or a line break, not typos.
84
138
  // before[k] = gaps before offset k, so "a gap inside s..e" costs the same however many gaps the block has.
85
139
  const before=new Uint32Array(text.length+1);for(const j of b.gaps||[])if(j>=0&&j<text.length)before[j+1]++;for(let k=1;k<=text.length;k++)before[k]+=before[k-1];
86
140
  const gap=(s,e)=>before[e]-before[s]>0,plain=(s,e)=>(!b.map||b.map[e-1]-b.map[s]===e-1-s)&&!gap(s,e),tech=technical(text);
87
- for(const [id,re] of TYPO)for(const m of text.matchAll(re))if(plain(m.index,m.index+m[0].length)&&!tech(m.index))add(i,m.index,m.index+m[0].length,id,BUILTIN[id]);
141
+ for(const [id,re,fix] of TYPO)for(const m of text.matchAll(re))if(plain(m.index,m.index+m[0].length)&&!tech(m.index))add(i,m.index,m.index+m[0].length,id,fix?{...BUILTIN[id],replace:[fix(m[0])],exact:true}:BUILTIN[id]);
88
142
  for(const m of text.matchAll(/\p{L}+/gu))if(MIXED.test(m[0])&&plain(m.index,m.index+m[0].length)&&!tech(m.index))add(i,m.index,m.index+m[0].length,'typo-mixed-script',BUILTIN['typo-mixed-script']);
89
143
  for(const m of text.matchAll(/"[^"\n]*"/g))if(/[А-Яа-яЁё]/.test(m[0]))add(i,m.index,m.index+m[0].length,'typo-quotes',BUILTIN['typo-quotes']);
90
144
  for(const k of unpaired(text))add(i,k,k+1,'typo-unpaired',BUILTIN['typo-unpaired']);