@7n/rules-lang-js 0.5.0 → 0.7.0

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/rules/style/admin_table/admin_table.mdc +88 -0
  4. package/rules/style/admin_table/concern.json +7 -0
  5. package/rules/style/admin_table/docs/index.md +9 -0
  6. package/rules/style/admin_table/docs/main.md +14 -0
  7. package/rules/style/admin_table/main.mjs +46 -0
  8. package/rules/style/colors/colors.mdc +21 -0
  9. package/rules/style/colors/concern.json +3 -0
  10. package/rules/style/docs/index.md +11 -0
  11. package/rules/style/gap/concern.json +7 -0
  12. package/rules/style/gap/docs/index.md +9 -0
  13. package/rules/style/gap/docs/main.md +15 -0
  14. package/rules/style/gap/gap.mdc +22 -0
  15. package/rules/style/gap/main.mjs +51 -0
  16. package/rules/style/lint/concern.json +7 -0
  17. package/rules/style/lint/docs/fix-lint.md +29 -0
  18. package/rules/style/lint/docs/index.md +11 -0
  19. package/rules/style/lint/docs/main.md +29 -0
  20. package/rules/style/lint/fix-lint.mjs +66 -0
  21. package/rules/style/lint/main.mjs +68 -0
  22. package/rules/style/main.json +1 -0
  23. package/rules/style/main.mdc +12 -0
  24. package/rules/style/package_json/concern.json +9 -0
  25. package/rules/style/package_json/docs/fix-package_json.md +26 -0
  26. package/rules/style/package_json/docs/index.md +9 -0
  27. package/rules/style/package_json/fix-package_json.mjs +3 -0
  28. package/rules/style/package_json/package_json.mdc +18 -0
  29. package/rules/style/package_json/package_json.rego +31 -0
  30. package/rules/style/package_json/template/package.json.snippet.json +5 -0
  31. package/rules/style/quasar/concern.json +3 -0
  32. package/rules/style/quasar/quasar.mdc +7 -0
  33. package/rules/style/quasar_fixes/concern.json +7 -0
  34. package/rules/style/quasar_fixes/docs/index.md +9 -0
  35. package/rules/style/quasar_fixes/docs/main.md +16 -0
  36. package/rules/style/quasar_fixes/main.mjs +57 -0
  37. package/rules/style/quasar_fixes/quasar_fixes.mdc +32 -0
  38. package/rules/style/tooling/concern.json +14 -0
  39. package/rules/style/tooling/docs/fix-tooling.md +29 -0
  40. package/rules/style/tooling/docs/index.md +12 -0
  41. package/rules/style/tooling/docs/main.md +34 -0
  42. package/rules/style/tooling/fix-tooling.mjs +58 -0
  43. package/rules/style/tooling/main.mjs +73 -0
  44. package/rules/style/tooling/tooling.mdc +85 -0
  45. package/rules/style/vscode_extensions/concern.json +9 -0
  46. package/rules/style/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  47. package/rules/style/vscode_extensions/docs/index.md +11 -0
  48. package/rules/style/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  49. package/rules/style/vscode_extensions/template/extensions.json.snippet.json +1 -0
  50. package/rules/style/vscode_extensions/vscode_extensions.mdc +13 -0
  51. package/rules/style/vscode_extensions/vscode_extensions.rego +13 -0
  52. package/rules/style/vscode_settings/concern.json +9 -0
  53. package/rules/style/vscode_settings/docs/fix-vscode_settings.md +26 -0
  54. package/rules/style/vscode_settings/docs/index.md +9 -0
  55. package/rules/style/vscode_settings/fix-vscode_settings.mjs +5 -0
  56. package/rules/style/vscode_settings/template/settings.json.snippet.json +5 -0
  57. package/rules/style/vscode_settings/vscode_settings.mdc +19 -0
  58. package/rules/style/vscode_settings/vscode_settings.rego +15 -0
  59. package/rules/test/docs/index.md +11 -0
  60. package/rules/test/lib/collect-test-file-offenders.mjs +49 -0
  61. package/rules/test/lib/docs/collect-test-file-offenders.md +29 -0
  62. package/rules/test/lib/docs/index.md +9 -0
  63. package/rules/test/location/concern.json +8 -0
  64. package/rules/test/location/docs/index.md +11 -0
  65. package/rules/test/location/docs/main.md +36 -0
  66. package/rules/test/location/location.mdc +52 -0
  67. package/rules/test/location/main.mjs +70 -0
  68. package/rules/test/main.json +1 -0
  69. package/rules/test/main.mdc +26 -0
  70. package/rules/test/no-bun-test-import/concern.json +7 -0
  71. package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +30 -0
  72. package/rules/test/no-bun-test-import/docs/index.md +10 -0
  73. package/rules/test/no-bun-test-import/docs/main.md +35 -0
  74. package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
  75. package/rules/test/no-bun-test-import/main.mjs +105 -0
  76. package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
  77. package/rules/test/no-console-store-restore/concern.json +7 -0
  78. package/rules/test/no-console-store-restore/docs/index.md +11 -0
  79. package/rules/test/no-console-store-restore/docs/main.md +44 -0
  80. package/rules/test/no-console-store-restore/main.mjs +55 -0
  81. package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
  82. package/rules/test/no-process-chdir/concern.json +7 -0
  83. package/rules/test/no-process-chdir/docs/index.md +11 -0
  84. package/rules/test/no-process-chdir/docs/main.md +32 -0
  85. package/rules/test/no-process-chdir/main.mjs +40 -0
  86. package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
  87. package/rules/test/no-relative-fs-path/concern.json +7 -0
  88. package/rules/test/no-relative-fs-path/docs/index.md +11 -0
  89. package/rules/test/no-relative-fs-path/docs/main.md +34 -0
  90. package/rules/test/no-relative-fs-path/main.mjs +239 -0
  91. package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
  92. package/rules/test/package_json/concern.json +11 -0
  93. package/rules/test/package_json/package_json.mdc +18 -0
  94. package/rules/test/package_json/package_json.rego +25 -0
  95. package/rules/test/package_json/template/package.json.contains.json +6 -0
  96. package/rules/test/sandbox-aware-test/concern.json +7 -0
  97. package/rules/test/sandbox-aware-test/docs/index.md +11 -0
  98. package/rules/test/sandbox-aware-test/docs/main.md +55 -0
  99. package/rules/test/sandbox-aware-test/main.mjs +89 -0
  100. package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
  101. package/rules/test/stryker_config/concern.json +8 -0
  102. package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
  103. package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
  104. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
  105. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
  106. package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
  107. package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
  108. package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
  109. package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
  110. package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
  111. package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
  112. package/rules/test/stryker_config/docs/fix-stryker_config.md +41 -0
  113. package/rules/test/stryker_config/docs/index.md +12 -0
  114. package/rules/test/stryker_config/docs/main.md +38 -0
  115. package/rules/test/stryker_config/fix-stryker_config.mjs +77 -0
  116. package/rules/test/stryker_config/main.mjs +504 -0
  117. package/rules/test/stryker_config/stryker_config.mdc +26 -0
  118. package/rules/test/vitest-api-conventions/concern.json +7 -0
  119. package/rules/test/vitest-api-conventions/docs/index.md +9 -0
  120. package/rules/test/vitest-api-conventions/docs/main.md +40 -0
  121. package/rules/test/vitest-api-conventions/main.mjs +153 -0
  122. package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
  123. package/rules/test/vitest-config-pool-forks/concern.json +7 -0
  124. package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
  125. package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
  126. package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
  127. package/rules/test/vitest-config-pool-forks/vitest-config-pool-forks.mdc +34 -0
@@ -0,0 +1,19 @@
1
+ ## .vscode/settings.json: вимкнення вбудованих CSS/SCSS/Less валідаторів
2
+
3
+ Rego-пакет: `style_lint.vscode_settings`
4
+
5
+ Цільовий файл: `.vscode/settings.json`
6
+
7
+ Leaf-by-leaf walker: перевіряє кожен ключ з template — його значення у `settings.json` має точно збігатися. Додаткові ключі у файлі — дозволені.
8
+
9
+ Канонічний template: [settings.json.snippet.json](./template/settings.json.snippet.json)
10
+
11
+ Обов'язкові значення:
12
+
13
+ - `"css.validate": false`
14
+ - `"less.validate": false`
15
+ - `"scss.validate": false`
16
+
17
+ **✓ Правильно** — усі три ключі присутні зі значенням `false`; можуть бути й інші ключі.
18
+
19
+ **✗ Неправильно** — будь-який з ключів відсутній, має значення `true` або рядок (`"off"`).
@@ -0,0 +1,15 @@
1
+ # Перевірка `.vscode/settings.json` для style (style.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/settings.json.snippet.json.
5
+ # Top-level літеральні keys — leaf-by-leaf walker.
6
+ package style.vscode_settings
7
+
8
+ import rego.v1
9
+
10
+ deny contains msg if {
11
+ some key, expected_value in data.template.snippet
12
+ actual := object.get(input, key, null)
13
+ actual != expected_value
14
+ msg := sprintf(".vscode/settings.json: \"%s\" має бути %v (style.mdc)", [key, expected_value])
15
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/test
4
+ resource: plugins/lang-js/rules/test/
5
+ ---
6
+
7
+ # npm/rules/test
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,49 @@
1
+ /** @see ../docs/collect-test-file-offenders.md */
2
+ import { readFile } from 'node:fs/promises'
3
+ import { basename, relative } from 'node:path'
4
+
5
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
6
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
7
+
8
+ /**
9
+ * Чи файл — JS-тест (`*.test.mjs` / `*.test.js`).
10
+ * @param {string} absPath абсолютний шлях
11
+ * @returns {boolean} `true` для `.test.{mjs,js}` файлів
12
+ */
13
+ export function isTestFile(absPath) {
14
+ const name = basename(absPath)
15
+ return name.endsWith('.test.mjs') || name.endsWith('.test.js')
16
+ }
17
+
18
+ /**
19
+ * Обходить репо (поважаючи `.n-rules.json:ignore`), збирає `*.test.{mjs,js}` файли
20
+ * і прогонить кожен через `findOffenders(body)` — спільний скелет для per-file
21
+ * regex/parse-based test-конвенцій (напр. `no-console-store-restore`, `vitest-api-conventions`).
22
+ * @param {string} cwd корінь репозиторію
23
+ * @param {(body: string) => Array<{line: number}>} findOffenders детектор порушень у тілі файлу
24
+ * @returns {Promise<{ testFiles: string[], offenders: Array<{file: string, line: number}> }>} усі тестові файли й знайдені порушення (з relative-шляхом)
25
+ */
26
+ export async function collectTestFileOffenders(cwd, findOffenders) {
27
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
28
+
29
+ /** @type {string[]} */
30
+ const testFiles = []
31
+ await walkDir(
32
+ cwd,
33
+ absPath => {
34
+ if (isTestFile(absPath)) testFiles.push(absPath)
35
+ },
36
+ ignorePaths
37
+ )
38
+
39
+ /** @type {Array<{file: string, line: number}>} */
40
+ const offenders = []
41
+ for (const absPath of testFiles) {
42
+ const body = await readFile(absPath, 'utf8')
43
+ for (const o of findOffenders(body)) {
44
+ offenders.push({ file: relative(cwd, absPath), ...o })
45
+ }
46
+ }
47
+
48
+ return { testFiles, offenders }
49
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: collect-test-file-offenders.mjs
4
+ resource: plugins/lang-js/rules/test/lib/collect-test-file-offenders.mjs
5
+ docgen:
6
+ crc: bf54977d
7
+ model: openai-codex/gpt-5.4-mini
8
+ score: 100
9
+ issues: judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Файл знаходить JS-тести `*.test.mjs` і `*.test.js` у репозиторії з урахуванням `.n-rules.json` та повертає для кожного з них відносні шляхи до offenders. Це дає змогу окремо перевіряти, які саме тестові файли виходять за межі правил проєкту, без додаткового парсингу чи нормалізації результату.
16
+
17
+ ## Поведінка
18
+
19
+ - `isTestFile` — визначає, чи шлях належить до JS-тесту `*.test.mjs` або `*.test.js`.
20
+ - `collectTestFileOffenders` — знаходить усі тестові файли в репозиторії з урахуванням `.n-rules.json` та збирає для них знайдені порушення з відносними шляхами.
21
+
22
+ ## Публічний API
23
+
24
+ - isTestFile — Розпізнає JS-файли з тестовою назвою `*.test.mjs` або `*.test.js`.
25
+ - collectTestFileOffenders — Обходить репозиторій з урахуванням `.n-rules.json:ignore`, знаходить `*.test.{mjs,js}` і проганяє кожен файл через `findOffenders` для спільних test-конвенцій на кшталт `no-console-store-restore` і `vitest-api-conventions`.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/test/lib
4
+ resource: plugins/lang-js/rules/test/lib/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ----------------------------------------------------------------- | --------- |
9
+ | [collect-test-file-offenders.mjs](collect-test-file-offenders.md) | JS Module |
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "structural",
4
+ "lint": {
5
+ "scope": "full",
6
+ "glob": ["**/*.test.mjs"]
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/test/location
4
+ resource: plugins/lang-js/rules/test/location/
5
+ ---
6
+
7
+ # npm/rules/test/location
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,36 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/test/location/main.mjs
5
+ docgen:
6
+ crc: 88077b29
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Огляд
16
+ Файл гарантує відповідність усіх тестових файлів встановленому розташуванню. Він сканує репозиторій, щоб підтвердити, що всі файли з розширенням `*.test.mjs` знаходяться у директорії `tests`.
17
+
18
+ Поведінка
19
+ Запускає `main` для перевірки коректності розташування всіх тестових файлів (`*.test.mjs`). Сканує репозиторій, ігноруючи директорії, визначені у конфігурації. Для кожного тестового файлу перевіряє його наявність у каталозі `tests`. Якщо тест виявлено поза директорією `tests`, фіксується його повний відносний шлях. У випадку відсутності порушень, інформується, що всі тестові файли розташовані коректно (test.mdc). Якщо виявлено порушення, для кожного файлу, що не відповідає правилам (test.mdc), повідомляється його шлях та необхідність переміщення до відповідної піддиректорії `tests`.
20
+
21
+ ## Поведінка
22
+
23
+ 1. Запускає `main` для перевірки розташування всіх тестових файлів з розширенням `*.test.mjs`.
24
+ 2. Сканує репозиторій, ігноруючи директорії, вказані в конфігурації.
25
+ 3. Для кожного знайденого тестового файлу перевіряє, чи він знаходиться в каталозі з назвою `tests`.
26
+ 4. Якщо тест знайдено поза директорією `tests`, фіксує його повний відносний шлях.
27
+ 5. Якщо порушень немає, повідомляє, що всі тестові файли розташовані коректно (test.mdc).
28
+ 6. Якщо порушення виявлено, для кожного файлу, що порушує правила (test.mdc), повідомляє шлях та вказує, що тест має бути переміщено до відповідної піддиректорії `tests`.
29
+
30
+ ## Публічний API
31
+
32
+ - main — Встановлює, чи знаходяться файли, призначені для тестування, у відповідному каталозі `tests/` (test.mdc).
33
+
34
+ ## Гарантії поведінки
35
+
36
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,52 @@
1
+ ## Розміщення тестових файлів у каталозі `tests/`
2
+
3
+ JS-тести у пакеті лежать у **піддиректорії `tests/`** поряд із кодом, який тестується, а **не безпосередньо біля джерельного файлу**.
4
+
5
+ ```
6
+ rules/foo/js/bar/
7
+ ├── check.mjs ← JS-джерело
8
+ └── tests/
9
+ └── check.test.mjs ← тест check.mjs
10
+ ```
11
+
12
+ **Чому так:**
13
+
14
+ - Каталог-з-кодом залишається чистим: продакшен-модулі, политики, темплейти не миготять серед тестових файлів.
15
+ - Один погляд на дерево показує, що піде у npm tarball (все, крім `tests/` і `__fixtures__/` — їх ловить `package.json#files` через негативні globs).
16
+ - Якщо тест переростає у мульти-файловий — він уже у власному каталозі, без хаотичного розкидання.
17
+
18
+ **Виняток — Rego unit-тести (`*_test.rego`):** лишаються **поряд із полісі-файлом** відповідно до загальноприйнятого OPA/Conftest community-патерну. `conftest verify -p <dir>` рекурсивно підхоплює їх незалежно від місця в каталозі.
19
+
20
+ ```
21
+ rules/foo/policy/package_json/
22
+ ├── package_json.rego ← Rego-правило
23
+ ├── package_json_test.rego ← Rego unit-тести (поряд, не у tests/)
24
+ └── target.json
25
+ ```
26
+
27
+ **Спеціальні випадки:**
28
+
29
+ - **Integration-тести на рівні пакета** — у `<root>/tests/` (наприклад `npm/tests/`). Це не «біля файлу», а виокремлений каталог для тестів, що покривають весь пакет.
30
+ - **Fixtures**: `__fixtures__/` (для shared) і `fixtures/` (для rule-specific) також живуть **усередині `tests/`**: `tests/__fixtures__/...` і `tests/fixtures/...`.
31
+ - **Test helpers** (`test-helpers.mjs`) можуть лишатися у `scripts/utils/` як shared infra — конвенція стосується файлів `*.test.mjs`.
32
+
33
+ **Гарантії tarball-чистоти** через `package.json#files`:
34
+
35
+ ```json
36
+ "files": [
37
+ "...",
38
+ "!**/*.test.mjs",
39
+ "!**/*_test.rego",
40
+ "!**/__fixtures__/**",
41
+ "!**/fixtures/**",
42
+ "!**/test-helpers.mjs"
43
+ ]
44
+ ```
45
+
46
+ Recursive globs ловлять файли всередині `tests/` так само, як ловили б їх біля джерела.
47
+
48
+ `npx @7n/rules fix test` (concern `location`) проходить деревом пакета й перевіряє: **кожен `*.test.mjs` файл лежить усередині каталогу з ім'ям `tests`** (точне співпадіння басенейму батьківської директорії). Файли поза цим каталогом репортуються з порадою куди їх перенести.
49
+
50
+ `*_test.rego` перевіркою **не охоплюються** — вони не переміщуються.
51
+
52
+ Пропускаються: `node_modules`, `.git`, `dist`, `build`, `.venv`, `venv`, шляхи з `.n-rules.json:ignore`.
@@ -0,0 +1,70 @@
1
+ /** @see ./docs/location.md */
2
+ import { basename, dirname, relative } from 'node:path'
3
+
4
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
6
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
7
+
8
+ const TESTS_DIR_NAME = 'tests'
9
+
10
+ /**
11
+ * Чи файл є JS-тестом (`*.test.mjs`).
12
+ * @param {string} absPath абсолютний шлях
13
+ * @returns {boolean} true для шляхів із суфіксом `.test.mjs`
14
+ */
15
+ function isTestFile(absPath) {
16
+ return basename(absPath).endsWith('.test.mjs')
17
+ }
18
+
19
+ /**
20
+ * Перевіряє, чи лежить тест у каталозі з іменем `tests`.
21
+ * @param {string} absPath абсолютний шлях до тесту
22
+ * @returns {boolean} `true`, якщо басенейм батьківської директорії — `tests`
23
+ */
24
+ function isInsideTestsDir(absPath) {
25
+ return basename(dirname(absPath)) === TESTS_DIR_NAME
26
+ }
27
+
28
+ /**
29
+ * Перевіряє розміщення тестових файлів у каталозі `tests/` (test.mdc).
30
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
31
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки
32
+ */
33
+ export async function lint(ctx) {
34
+ const reporter = createViolationReporter(ctx)
35
+ const { pass, fail } = reporter
36
+
37
+ const cwd = ctx.cwd
38
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
39
+
40
+ /** @type {string[]} */
41
+ const offenders = []
42
+ let totalTests = 0
43
+
44
+ await walkDir(
45
+ cwd,
46
+ absPath => {
47
+ if (!isTestFile(absPath)) {
48
+ return
49
+ }
50
+ totalTests++
51
+ if (!isInsideTestsDir(absPath)) {
52
+ offenders.push(relative(cwd, absPath))
53
+ }
54
+ },
55
+ ignorePaths
56
+ )
57
+
58
+ if (offenders.length === 0) {
59
+ pass(`Всі ${totalTests} файлів *.test.mjs у каталозі tests/ (test.mdc)`)
60
+ return reporter.result()
61
+ }
62
+
63
+ for (const offenderPath of offenders) {
64
+ const parentDir = dirname(offenderPath)
65
+ const base = basename(offenderPath)
66
+ fail(`${offenderPath}: тест має лежати у tests/ — перенеси у ${parentDir}/${TESTS_DIR_NAME}/${base} (test.mdc)`)
67
+ }
68
+
69
+ return reporter.result()
70
+ }
@@ -0,0 +1 @@
1
+ { "auto": "завжди" }
@@ -0,0 +1,26 @@
1
+ ---
2
+ description: JS-тести (*.test.mjs) живуть у tests/. Правило `test` керує stryker.config.mjs + vitest.config.mjs (конфіг cargo-mutants — концерн cargo_mutants_config правила rust у @7n/rules-lang-rust).
3
+ version: '2.8'
4
+ globs: "**/{.n-rules.json,package.json,stryker.config.mjs,vitest.config.mjs,vitest.config.js},**/*.test.mjs"
5
+ alwaysApply: false
6
+ ---
7
+
8
+ Правило **test** керує розміщенням тестових файлів, безпекою ізоляції тестів (заборона `process.chdir`, відносних шляхів у FS, ручного store/restore console), налаштуванням Vitest/Stryker baseline. Конфігурація cargo-mutants для Rust — концерн `cargo_mutants_config` правила `rust` (@7n/rules-lang-rust).
9
+
10
+ ## Покриття + мутаційне тестування
11
+
12
+ Канонічна команда — `npx @7n/test coverage` (пакет `@7n/test`, **не** `@7n/rules` — цей пакет лише постачає providers, оркестратор живе окремо): збирає метрики покриття (`vitest run --coverage`, `cargo llvm-cov` тощо) і мутаційного тестування (Stryker з vitest-runner + `coverageAnalysis: 'perTest'`, `cargo-mutants`) з усіх активних провайдерів у `.n-rules.json#rules` і пише `COVERAGE.md` у корінь проєкту.
13
+
14
+ **Scoped-режим `--changed`:** `npx @7n/test coverage --changed` звужує scope до файлів, змінених від git merge-base поточної гілки з `main` або `origin/main`. У changed-режимі `COVERAGE.md` **не** перезаписується і LLM-класифікація не запускається.
15
+
16
+ **`--fix`:** `npx @7n/test coverage --fix` — після збору метрик агент виправляє survived-мутанти й повторює coverage.
17
+
18
+ Провайдери живуть у `npm/rules/<rule>/coverage/coverage.mjs` (цей пакет, `@7n/rules`). Оркестратор — у `@7n/test` (`npm/src/coverage/coverage.mjs`), який резолвить providers з `node_modules/@7n/rules/rules`. Проєкт-споживач має мати `@7n/test` як devDependency, аби `npx @7n/test coverage` резолвився без мережевого fetch щоразу.
19
+
20
+ У `package.json` (корінь) має бути `scripts.coverage` із викликом `npx @7n/test coverage`.
21
+
22
+ ### Multi-workspace iteration
23
+
24
+ У monorepo `@7n/test coverage` ітерує усі workspaces з власним `package.json` і агрегує метрики lcov + Stryker у єдиний `JS`-рядок `COVERAGE.md`. Workspace без тестів пропускається без помилки.
25
+
26
+ - [package.json.contains.json](./package_json/template/package.json.contains.json)
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.test.mjs", "**/*.test.js"]
6
+ }
7
+ }
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-no-bun-test-import.mjs
4
+ resource: plugins/lang-js/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs
5
+ docgen:
6
+ crc: 06d778e7
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
10
+ ---
11
+
12
+ ## Огляд
13
+
14
+ Цей файл забезпечує автоматичне виправлення імпортів у контексті `test/no-bun-test-import`. Він сканує файли та замінює декларації імпорту з `bun:test` на їхні еквіваленти з `vitest` (такі як `describe`, `test`, `it`, `expect`, `beforeEach`, `beforeAll`, `afterEach`, `afterAll`). Мета полягає в уніфікації тестового середовища за допомогою `vitest`. Зауважте, що імпорти, які містять `mock`, `spyOn` або `jest`, не підлягають автоматичній заміні, оскільки вони вимагають ручного коригування місць виклику API (`vi.fn`/`vi.spyOn`).
15
+
16
+ ## Поведінка
17
+
18
+ Поведінка:
19
+ 1. Ініціалізувати процес автофіксу для виявлених порушень, пов'язаних із використанням `bun-test-import`.
20
+ 2. Ітеративно обробити кожен файл, у якому виявлено такі порушення.
21
+ 3. Для кожного файлу прочитати його вміст.
22
+ 4. Виявити імпорти з `bun:test`, які можуть бути замінені на еквівалент у `vitest`.
23
+ 5. Замінити ці імпорти з кінця файлу до початку, щоб уникнути зсуву позицій наступних матчів.
24
+ 6. Записати змінений вміст назад у файл.
25
+ 7. Після обробки всіх файлів повернути перелік змінених файлів та повідомлення про застосовані зміни.
26
+ 8. Використовувати наданий об'єкт `patterns` для визначення логіки обробки.
27
+
28
+ ## Гарантії поведінки
29
+
30
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,10 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/test/no-bun-test-import
4
+ resource: plugins/lang-js/rules/test/no-bun-test-import/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------------------------------------------- | --------- |
9
+ | [fix-no-bun-test-import.mjs](fix-no-bun-test-import.md) | JS Module |
10
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/test/no-bun-test-import/main.mjs
5
+ docgen:
6
+ crc: 379b9fee
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Огляд
16
+ Цей компонент аналізує тестові файли проекту для модернізації тестового фреймворку. Він ідентифікує імпорти з `bun:test` та перевіряє їхню сумісність для потенційного переходу на `vitest`, згенерувавши відповідні порушення для виправлення.
17
+
18
+ Поведінка
19
+ findBunTestImports виявляє імпорти з `bun:test` у коді, визначаючи, які з них можуть бути безпечно замінені на `vitest`.
20
+ lint перевіряє тестові файли на наявність імпортів з `bun:test`, генеруючи порушення, якщо імпорт не може бути автоматично виправлений (test.mdc).
21
+
22
+ ## Поведінка
23
+
24
+ Поведінка
25
+ findBunTestImports знаходить усі імпорти з `bun:test` у коді, визначаючи, які з них можуть бути безпечно замінені на `vitest`.
26
+ lint перевіряє тестові файли на наявність імпортів з `bun:test`, генеруючи порушення, якщо імпорт не може бути автоматично виправлений (test.mdc).
27
+
28
+ ## Публічний API
29
+
30
+ findBunTestImports — шукає усі імпорти з `bun:test` у коді файлу, щоб уникнути повторного аналізу.
31
+ lint — гарантує, що тестові файли не імпортують модулі з `bun:test`, оскільки Vitest не може їх знайти.
32
+
33
+ ## Гарантії поведінки
34
+
35
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,51 @@
1
+ /**
2
+ * T0-autofix для `test/no-bun-test-import`: переписує `from 'bun:test'` на
3
+ * `from 'vitest'` у import-декларацій, чиї специфікатори мають прямий 1:1
4
+ * еквівалент у vitest (describe/test/it/expect/beforeEach/beforeAll/afterEach/afterAll). Import-и з
5
+ * mock/spyOn/jest тощо НЕ чіпаються — там потрібне ручне виправлення call-sites
6
+ * (`vi.fn`/`vi.spyOn` мають інший API за межами самого import), детектор лишає
7
+ * їх як violation для ручного/LLM-ladder фіксу.
8
+ */
9
+ import { readFile, writeFile } from 'node:fs/promises'
10
+ import { join } from 'node:path'
11
+
12
+ import { findBunTestImports } from './main.mjs'
13
+
14
+ const QUOTED_BUN_TEST_RE = /(['"])bun:test\1/u
15
+
16
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
17
+ export const patterns = [
18
+ {
19
+ id: 'rewrite-bun-test-import-to-vitest',
20
+ test: violations => violations.some(v => v.reason === 'bun-test-import' && v.data?.fixable),
21
+ apply: async (violations, ctx) => {
22
+ const files = [
23
+ ...new Set(violations.filter(v => v.reason === 'bun-test-import' && v.data?.fixable).map(v => v.file))
24
+ ]
25
+
26
+ const touchedFiles = []
27
+ for (const file of files) {
28
+ const absPath = join(ctx.cwd, file)
29
+ const content = await readFile(absPath, 'utf8')
30
+ const found = findBunTestImports(content)
31
+ if (found.length === 0) continue
32
+
33
+ let next = content
34
+ // Заміняємо з кінця файлу до початку, щоб офсети попередніх матчів не зсувались.
35
+ for (const imp of found.toReversed()) {
36
+ if (!imp.fixable) continue
37
+ const rewritten = imp.raw.replace(QUOTED_BUN_TEST_RE, (_match, quote) => `${quote}vitest${quote}`)
38
+ next = next.slice(0, imp.start) + rewritten + next.slice(imp.end)
39
+ }
40
+ if (next === content) continue
41
+
42
+ ctx.recordWrite?.(absPath)
43
+ await writeFile(absPath, next, 'utf8')
44
+ touchedFiles.push(absPath)
45
+ }
46
+
47
+ if (touchedFiles.length === 0) return { touchedFiles: [] }
48
+ return { touchedFiles, message: `bun:test → vitest: ${touchedFiles.join(', ')}` }
49
+ }
50
+ }
51
+ ]
@@ -0,0 +1,105 @@
1
+ /** @see ./docs/no-bun-test-import.md */
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ import { collectTestFiles, toRelPosix } from '@7n/rules/scripts/lib/collect-test-files.mjs'
5
+
6
+ /**
7
+ * Іменовані експорти `bun:test`, що мають прямий 1:1 еквівалент у vitest (той самий
8
+ * API, лише інше джерело пакета) — безпечно переписати джерело без чіпання call-sites.
9
+ * `mock`/`spyOn`/`jest` та інше НЕ входять сюди: у vitest вони живуть під `vi.*`
10
+ * namespace (`vi.fn`, `vi.spyOn`), тож переписування джерела без переписування
11
+ * call-sites дало б робочий import, але зламаний виклик — небезпечно для T0.
12
+ */
13
+ const SAFE_SPECIFIERS = new Set([
14
+ 'describe',
15
+ 'test',
16
+ 'it',
17
+ 'expect',
18
+ 'beforeEach',
19
+ 'beforeAll',
20
+ 'afterEach',
21
+ 'afterAll'
22
+ ])
23
+
24
+ /**
25
+ * Іменований import з `bun:test`; специфікатор-секція може бути багаторядковою.
26
+ * `[^}]*` замість `[\s\S]*?` — уникає super-linear backtracking (sonarjs) при `\s*`
27
+ * навколо групи: символьний клас без альтернативи backtrack-у на межі `}`.
28
+ */
29
+ const BUN_TEST_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*(['"])bun:test\2/gu
30
+ /** Розділювач токенів специфікатора (замість `\s+as\s+`-regex, sonarjs/super-linear-regex). */
31
+ const WHITESPACE_RE = /\s+/u
32
+
33
+ /**
34
+ * Розбирає список іменованих специфікаторів `{ a, b as c }` на `{imported, local}`.
35
+ * @param {string} raw вміст фігурних дужок import-декларації
36
+ * @returns {{imported:string, local:string}[]} розібрані специфікатори
37
+ */
38
+ function parseSpecifiers(raw) {
39
+ return raw
40
+ .split(',')
41
+ .map(s => s.trim())
42
+ .filter(Boolean)
43
+ .map(s => {
44
+ // Токенізація замість `\s+as\s+`-regex (sonarjs/super-linear-regex на подвійному `\s+`).
45
+ const tokens = s.split(WHITESPACE_RE).filter(Boolean)
46
+ const asIndex = tokens.indexOf('as')
47
+ const imported = asIndex === -1 ? tokens.join(' ') : tokens.slice(0, asIndex).join(' ')
48
+ const local = asIndex === -1 ? imported : tokens.slice(asIndex + 1).join(' ')
49
+ return { imported, local }
50
+ })
51
+ }
52
+
53
+ /**
54
+ * Знаходить усі `import { ... } from 'bun:test'` у вмісті файлу. Спільна логіка
55
+ * для detector-а (`lint`) і T0-фіксера (`fix-no-bun-test-import.mjs`) — уникаємо
56
+ * дублювання парсингу.
57
+ * @param {string} content вихідний код файлу
58
+ * @returns {{start:number, end:number, raw:string, specifiers:{imported:string,local:string}[], fixable:boolean}[]} знайдені import-декларації
59
+ */
60
+ export function findBunTestImports(content) {
61
+ const matches = []
62
+ for (const m of content.matchAll(BUN_TEST_IMPORT_RE)) {
63
+ const specifiers = parseSpecifiers(m[1])
64
+ const fixable = specifiers.length > 0 && specifiers.every(s => SAFE_SPECIFIERS.has(s.imported))
65
+ matches.push({ start: m.index, end: m.index + m[0].length, raw: m[0], specifiers, fixable })
66
+ }
67
+ return matches
68
+ }
69
+
70
+ /**
71
+ * Detector: жоден `*.test.{mjs,js}` не імпортує з `bun:test` — vitest (яким
72
+ * запускається `@7n/test coverage` і `vitest run`) цей пакет не резолвить (test.mdc).
73
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту (`cwd` тощо)
74
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат лінту зі списком violations
75
+ */
76
+ export async function lint(ctx) {
77
+ const { cwd } = ctx
78
+ const testFiles = await collectTestFiles(cwd)
79
+
80
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintViolation[]} */
81
+ const violations = []
82
+ for (const absPath of testFiles) {
83
+ const body = await readFile(absPath, 'utf8')
84
+ const found = findBunTestImports(body)
85
+ if (found.length === 0) continue
86
+ const file = toRelPosix(cwd, absPath)
87
+ for (const imp of found) {
88
+ const line = body.slice(0, imp.start).split('\n').length
89
+ const specNames = imp.specifiers.map(s => s.imported).join(', ')
90
+ violations.push(
91
+ /** @type {Partial<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintViolation>} */ ({
92
+ reason: 'bun-test-import',
93
+ message: imp.fixable
94
+ ? `${file}:${line}: import з 'bun:test' — vitest не резолвить цей пакет; auto-fix перепише джерело на 'vitest' (test.mdc)`
95
+ : `${file}:${line}: import з 'bun:test' містить специфікатори без прямого 1:1 еквіваленту у vitest ` +
96
+ `(${specNames}) — потрібне ручне виправлення call-sites (vi.fn/vi.spyOn мають інший API) (test.mdc)`,
97
+ file,
98
+ data: { fixable: imp.fixable, specifiers: imp.specifiers.map(s => s.imported) }
99
+ })
100
+ )
101
+ }
102
+ }
103
+
104
+ return { violations }
105
+ }
@@ -0,0 +1,9 @@
1
+ ## Заборона `import ... from 'bun:test'`
2
+
3
+ Тести проєкту виконуються через **vitest** (`vitest run`, а також `@7n/test coverage`, який ганяє `vitest run --coverage` + Stryker з `vitest-runner`) — `bun:test` там не резолвиться взагалі: `Cannot find package 'bun:test'`. Файл з таким імпортом ламає весь coverage/mutation-прогін workspace-а (реальні помилки не маскуються — одна `bun:test`-згадка валить весь `@7n/test coverage`).
4
+
5
+ - **Заборонено** `import { ... } from 'bun:test'` у будь-якому `*.test.{mjs,js}`.
6
+ - **Auto-fix (T0):** якщо всі імпортовані специфікатори мають прямий 1:1 еквівалент у vitest — `describe`, `test`, `it`, `expect`, `beforeEach`, `beforeAll`, `afterEach`, `afterAll` — джерело переписується на `'vitest'` без зміни специфікаторів чи call-sites.
7
+ - **Без auto-fix:** якщо import містить `mock`, `spyOn`, `jest` чи інший специфікатор без прямого еквіваленту — T0 файл не чіпає. У vitest ці API живуть під `vi.*` namespace (`vi.fn()`, `vi.spyOn(...)`), тож самого перезапису джерела недостатньо — потрібне ручне (або LLM-ladder) виправлення call-sites. Violation залишається до фіксу.
8
+
9
+ **Перевірка** (`rules/test/no-bun-test-import/main.mjs`): сканує `**/*.test.{mjs,js}`, шукає `import { ... } from 'bun:test'` (можливо багаторядковий), парсить специфікатори і визначає `fixable`.
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.test.mjs", "**/*.test.js"]
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/test/no-console-store-restore
4
+ resource: plugins/lang-js/rules/test/no-console-store-restore/
5
+ ---
6
+
7
+ # npm/rules/test/no-console-store-restore
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |