@7n/rules-lang-js 0.6.0 → 0.7.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +3 -2
  3. package/rules/js-bun-redis/lib/docs/redis-imports.md +2 -2
  4. package/rules/style/admin_table/docs/main.md +1 -1
  5. package/rules/style/admin_table/main.mjs +2 -2
  6. package/rules/style/gap/docs/main.md +1 -1
  7. package/rules/style/gap/main.mjs +2 -2
  8. package/rules/style/lint/docs/fix-lint.md +1 -1
  9. package/rules/style/lint/docs/main.md +1 -1
  10. package/rules/style/lint/fix-lint.mjs +1 -1
  11. package/rules/style/lint/main.mjs +18 -6
  12. package/rules/style/quasar_fixes/docs/main.md +1 -1
  13. package/rules/style/quasar_fixes/main.mjs +2 -2
  14. package/rules/style/tooling/docs/fix-tooling.md +1 -1
  15. package/rules/style/tooling/docs/main.md +1 -1
  16. package/rules/style/tooling/fix-tooling.mjs +1 -1
  17. package/rules/style/tooling/main.mjs +2 -2
  18. package/rules/test/docs/index.md +11 -0
  19. package/rules/test/lib/collect-test-file-offenders.mjs +49 -0
  20. package/rules/test/lib/docs/collect-test-file-offenders.md +29 -0
  21. package/rules/test/lib/docs/index.md +9 -0
  22. package/rules/test/location/concern.json +8 -0
  23. package/rules/test/location/docs/index.md +11 -0
  24. package/rules/test/location/docs/main.md +36 -0
  25. package/rules/test/location/location.mdc +52 -0
  26. package/rules/test/location/main.mjs +70 -0
  27. package/rules/test/main.json +1 -0
  28. package/rules/test/main.mdc +26 -0
  29. package/rules/test/no-bun-test-import/concern.json +7 -0
  30. package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +30 -0
  31. package/rules/test/no-bun-test-import/docs/index.md +10 -0
  32. package/rules/test/no-bun-test-import/docs/main.md +35 -0
  33. package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
  34. package/rules/test/no-bun-test-import/main.mjs +105 -0
  35. package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
  36. package/rules/test/no-console-store-restore/concern.json +7 -0
  37. package/rules/test/no-console-store-restore/docs/index.md +11 -0
  38. package/rules/test/no-console-store-restore/docs/main.md +44 -0
  39. package/rules/test/no-console-store-restore/main.mjs +55 -0
  40. package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
  41. package/rules/test/no-process-chdir/concern.json +7 -0
  42. package/rules/test/no-process-chdir/docs/index.md +11 -0
  43. package/rules/test/no-process-chdir/docs/main.md +32 -0
  44. package/rules/test/no-process-chdir/main.mjs +40 -0
  45. package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
  46. package/rules/test/no-relative-fs-path/concern.json +7 -0
  47. package/rules/test/no-relative-fs-path/docs/index.md +11 -0
  48. package/rules/test/no-relative-fs-path/docs/main.md +34 -0
  49. package/rules/test/no-relative-fs-path/main.mjs +239 -0
  50. package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
  51. package/rules/test/package_json/concern.json +11 -0
  52. package/rules/test/package_json/package_json.mdc +18 -0
  53. package/rules/test/package_json/package_json.rego +25 -0
  54. package/rules/test/package_json/template/package.json.contains.json +6 -0
  55. package/rules/test/sandbox-aware-test/concern.json +7 -0
  56. package/rules/test/sandbox-aware-test/docs/index.md +11 -0
  57. package/rules/test/sandbox-aware-test/docs/main.md +55 -0
  58. package/rules/test/sandbox-aware-test/main.mjs +89 -0
  59. package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
  60. package/rules/test/stryker_config/concern.json +8 -0
  61. package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
  62. package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
  63. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
  64. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
  65. package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
  66. package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
  67. package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
  68. package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
  69. package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
  70. package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
  71. package/rules/test/stryker_config/docs/fix-stryker_config.md +41 -0
  72. package/rules/test/stryker_config/docs/index.md +12 -0
  73. package/rules/test/stryker_config/docs/main.md +38 -0
  74. package/rules/test/stryker_config/fix-stryker_config.mjs +77 -0
  75. package/rules/test/stryker_config/main.mjs +504 -0
  76. package/rules/test/stryker_config/stryker_config.mdc +26 -0
  77. package/rules/test/vitest-api-conventions/concern.json +7 -0
  78. package/rules/test/vitest-api-conventions/docs/index.md +9 -0
  79. package/rules/test/vitest-api-conventions/docs/main.md +40 -0
  80. package/rules/test/vitest-api-conventions/main.mjs +153 -0
  81. package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
  82. package/rules/test/vitest-config-pool-forks/concern.json +7 -0
  83. package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
  84. package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
  85. package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
  86. package/rules/test/vitest-config-pool-forks/vitest-config-pool-forks.mdc +34 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-07-20
4
+
5
+ ### Fixed
6
+
7
+ - style/lint: stylelint — задекларована залежність плагіна (раніше резолвилась лише транзитивно у цьому монорепо через @nitra/stylelint-config); відсутність тула тепер дає видимий warn-diagnostic замість мовчазного no-op (незалежний консюмер бачив би 'зелений' style-лінт, який насправді нічого не перевіряв)
8
+
9
+ ## [0.7.0] - 2026-07-19
10
+
11
+ ### Added
12
+
13
+ - правило test з ядра: розміщення тест-файлів у tests/, ізоляція (no-process-chdir, no-relative-fs-path, no-console-store-restore, sandbox-aware-test, no-bun-test-import), канон vitest/stryker конфігів і vitest-api-конвенції — без cargo-mutants (він у правилі rust плагіна lang-rust)
14
+
3
15
  ## [0.6.0] - 2026-07-19
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules-lang-js",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Плагін @7n/rules: JS/npm/bun-екосистема — lint-правила (js/bun/vue/js-run/npm-module/db), taze-провайдер (package.json, bunx taze) і doc-files-екстрактори (oxc AST)",
5
5
  "keywords": [
6
6
  "javascript",
@@ -58,7 +58,8 @@
58
58
  "jscpd": "^5.0.11",
59
59
  "knip": "^6.22.0",
60
60
  "oxc-parser": "^0.137.0",
61
- "oxlint": "^1.68.0"
61
+ "oxlint": "^1.68.0",
62
+ "stylelint": "^17.6.0"
62
63
  },
63
64
  "peerDependencies": {
64
65
  "@7n/rules": ">=1.27.0"
@@ -122,7 +122,7 @@ export function findRedisImportsInText(
122
122
 
123
123
  ### Внутрішні (репозиторій)
124
124
 
125
- Імпорт із `'../../../scripts/utils/ast-scan-utils.mjs'` — спільні утиліти AST-сканера для всіх правил у `npm/rules/*/lib/`:
125
+ Імпорт із `'@7n/rules/scripts/utils/ast-scan-utils.mjs'` (ядро) — спільні утиліти AST-сканера, які використовують правила з декількох плагінів:
126
126
 
127
127
  - `dynamicImportModule(node)` — якщо вузол AST є динамічним `import('mod')`, повертає рядок `'mod'`, інакше `null`/`undefined`.
128
128
  - `langFromPath(path)` — обчислює `lang` для `oxc-parser` за розширенням шляху.
@@ -198,7 +198,7 @@ import x from 'my-redis-helpers' // довільний сторонній пак
198
198
  Контрольний перелік, за яким можна **відтворити** функціонал модуля «з нуля»:
199
199
 
200
200
  1. Створити файл `redis-imports.mjs`, що імпортує `parseSync` з `oxc-parser`.
201
- 2. Імпортувати з `'../../../scripts/utils/ast-scan-utils.mjs'` шість утиліт: `dynamicImportModule`, `langFromPath`, `normalizeSnippet`, `offsetToLine`, `requireCallModule`, `walkAstWithAncestors`.
201
+ 2. Імпортувати з `'@7n/rules/scripts/utils/ast-scan-utils.mjs'` шість утиліт: `dynamicImportModule`, `langFromPath`, `normalizeSnippet`, `offsetToLine`, `requireCallModule`, `walkAstWithAncestors`.
202
202
  3. Оголосити константу `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u`.
203
203
  4. Оголосити константу `FORBIDDEN_MODULE_NAMES` як `Set` із елементами: `'ioredis'`, `'node-redis'`, `'redis'`, `'@redis/client'`, `'@redis/json'`, `'@redis/search'`, `'@redis/time-series'`, `'@redis/bloom'`.
204
204
  5. Реалізувати приватну функцію `isForbiddenRedisModule(mod)`: повертає `true`, якщо `FORBIDDEN_MODULE_NAMES.has(mod)` **або** `mod.startsWith('ioredis/')` **або** `mod.startsWith('redis/')` **або** `mod.startsWith('@redis/')`.
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/style/admin_table/main.mjs
5
5
  docgen:
6
- crc: 267496d1
6
+ crc: fb8f81a5
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -13,8 +13,8 @@ const DEFINITION_RE = /\.n-admin-table\b/u
13
13
  * клас `n-admin-table`, він має бути визначений хоч в одному `.scss`/`.css`/`.vue`
14
14
  * (guide: `admin_table.mdc`). Для усунення false positives — крос-файлова перевірка,
15
15
  * тож потребує whole-repo сканування незалежно від `ctx.files`.
16
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
16
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
18
  */
19
19
  export async function lint(ctx) {
20
20
  const reporter = createViolationReporter(ctx)
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/style/gap/main.mjs
5
5
  docgen:
6
- crc: 23cd7951
6
+ crc: 5d9d76e7
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -13,8 +13,8 @@ const DEFINITION_RE = /\.n-gap-(xs|sm|md|lg)\b/gu
13
13
  * використаний у `.vue`, має бути визначений хоч в одному `.scss`/`.css`/`.vue`
14
14
  * (guide: `gap.mdc`). Крос-файлова перевірка usage↔definition — потребує whole-repo
15
15
  * сканування незалежно від `ctx.files`.
16
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
16
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
18
  */
19
19
  export async function lint(ctx) {
20
20
  const reporter = createViolationReporter(ctx)
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: fix-lint.mjs
4
4
  resource: plugins/lang-js/rules/style/lint/fix-lint.mjs
5
5
  docgen:
6
- crc: a63dc24c
6
+ crc: a44c6b01
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/style/lint/main.mjs
5
5
  docgen:
6
- crc: c9a94f80
6
+ crc: 25130f97
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.99
@@ -40,7 +40,7 @@ async function listStyleFiles(cwd, ctxFiles) {
40
40
  return (r.stdout ?? '').split('\0').filter(Boolean)
41
41
  }
42
42
 
43
- /** @type {import('../../../scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
43
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
44
44
  export const patterns = [
45
45
  {
46
46
  id: 'style-stylelint-fix',
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * lint-поверхня style: read-only detector (stylelint без --fix) для css/scss/vue.
3
- * Per-file (ctx.files) або весь проєкт (ctx.files === undefined). Якщо stylelint
4
- * недоступний крок пропускається (без npx-автовстановлення).
3
+ * Per-file (ctx.files) або весь проєкт (ctx.files === undefined). `stylelint`
4
+ * задекларована залежність плагіна (справжній `npm install` завжди його ставить);
5
+ * якщо резолв все ж не вдався (побитий install, hoisting-аномалія) — крок
6
+ * пропускається з видимим warn-diagnostic, а не мовчки (без npx-автовстановлення).
5
7
  */
6
8
  import { existsSync } from 'node:fs'
7
9
  import { join } from 'node:path'
@@ -33,8 +35,8 @@ export function resolveStylelint(cwd) {
33
35
 
34
36
  /**
35
37
  * Detector style/lint (read-only).
36
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd, files)
37
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
38
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd, files)
39
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
38
40
  */
39
41
  export async function lint(ctx) {
40
42
  const reporter = createViolationReporter(ctx)
@@ -54,8 +56,18 @@ export async function lint(ctx) {
54
56
 
55
57
  const stylelint = resolveStylelint(cwd)
56
58
  if (!stylelint) {
57
- // stylelint недоступний крок пропущено (без автовстановлення)
58
- return reporter.result()
59
+ // stylelint задекларована залежність плагіна, тож відсутність тут аномальна
60
+ // (побитий install, hoisting) — крок пропущено з видимим warn, не мовчки.
61
+ const result = reporter.result()
62
+ result.diagnostics = [
63
+ {
64
+ level: 'warn',
65
+ message:
66
+ 'lint-style: `stylelint` не резолвиться (ні node_modules/.bin, ні PATH) — CSS/SCSS/Vue-стилі НЕ перевірені ' +
67
+ 'цим прогоном (style.mdc). `stylelint` — залежність @7n/rules-lang-js; переустанови плагін, якщо бачиш це.'
68
+ }
69
+ ]
70
+ return result
59
71
  }
60
72
 
61
73
  const r = await spawnAsync(stylelint, targets, { cwd, shell: false })
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/style/quasar_fixes/main.mjs
5
5
  docgen:
6
- crc: 0f9c4631
6
+ crc: dec47808
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -21,8 +21,8 @@ const FIXES = [
21
21
  * `<q-scroll-area>` або `<q-tooltip>`, у `.scss`/`.css`/`.vue` має бути відповідний
22
22
  * CSS-фікс (guide: `quasar_fixes.mdc`). Крос-файлова перевірка usage↔definition —
23
23
  * потребує whole-repo сканування незалежно від `ctx.files`.
24
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
25
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
24
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
25
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
26
26
  */
27
27
  export async function lint(ctx) {
28
28
  const reporter = createViolationReporter(ctx)
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: fix-tooling.mjs
4
4
  resource: plugins/lang-js/rules/style/tooling/fix-tooling.mjs
5
5
  docgen:
6
- crc: a25e47d2
6
+ crc: 033af5f9
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/style/tooling/main.mjs
5
5
  docgen:
6
- crc: c96a4b01
6
+ crc: aeb36134
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
9
  issues: anchor-miss:extensions.json,anchor-miss:settings.json,judge:inaccurate:0.99
@@ -12,7 +12,7 @@ const STYLELINTIGNORE_MISSING_RE = /\.stylelintignore не існує/u
12
12
  const STYLELINTIGNORE_NO_DIST_RE = /\.stylelintignore не містить рядка dist\//u
13
13
  const NO_STYLELINT_CONFIG_RE = /Немає конфігу stylelint/u
14
14
 
15
- /** @type {import('../../../scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
15
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
16
16
  export const patterns = [
17
17
  {
18
18
  id: 'style-stylelintignore-create',
@@ -45,8 +45,8 @@ async function checkStylelintConfigPresence(reporter, cwd) {
45
45
 
46
46
  /**
47
47
  * Перевіряє відповідність проєкту правилам style.mdc
48
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd тощо)
49
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
48
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd тощо)
49
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
50
50
  */
51
51
  export async function lint(ctx) {
52
52
  const cwd = ctx.cwd
@@ -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 |