@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.
- package/CHANGELOG.md +12 -0
- package/package.json +3 -2
- package/rules/js-bun-redis/lib/docs/redis-imports.md +2 -2
- package/rules/style/admin_table/docs/main.md +1 -1
- package/rules/style/admin_table/main.mjs +2 -2
- package/rules/style/gap/docs/main.md +1 -1
- package/rules/style/gap/main.mjs +2 -2
- package/rules/style/lint/docs/fix-lint.md +1 -1
- package/rules/style/lint/docs/main.md +1 -1
- package/rules/style/lint/fix-lint.mjs +1 -1
- package/rules/style/lint/main.mjs +18 -6
- package/rules/style/quasar_fixes/docs/main.md +1 -1
- package/rules/style/quasar_fixes/main.mjs +2 -2
- package/rules/style/tooling/docs/fix-tooling.md +1 -1
- package/rules/style/tooling/docs/main.md +1 -1
- package/rules/style/tooling/fix-tooling.mjs +1 -1
- package/rules/style/tooling/main.mjs +2 -2
- package/rules/test/docs/index.md +11 -0
- package/rules/test/lib/collect-test-file-offenders.mjs +49 -0
- package/rules/test/lib/docs/collect-test-file-offenders.md +29 -0
- package/rules/test/lib/docs/index.md +9 -0
- package/rules/test/location/concern.json +8 -0
- package/rules/test/location/docs/index.md +11 -0
- package/rules/test/location/docs/main.md +36 -0
- package/rules/test/location/location.mdc +52 -0
- package/rules/test/location/main.mjs +70 -0
- package/rules/test/main.json +1 -0
- package/rules/test/main.mdc +26 -0
- package/rules/test/no-bun-test-import/concern.json +7 -0
- package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +30 -0
- package/rules/test/no-bun-test-import/docs/index.md +10 -0
- package/rules/test/no-bun-test-import/docs/main.md +35 -0
- package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
- package/rules/test/no-bun-test-import/main.mjs +105 -0
- package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
- package/rules/test/no-console-store-restore/concern.json +7 -0
- package/rules/test/no-console-store-restore/docs/index.md +11 -0
- package/rules/test/no-console-store-restore/docs/main.md +44 -0
- package/rules/test/no-console-store-restore/main.mjs +55 -0
- package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
- package/rules/test/no-process-chdir/concern.json +7 -0
- package/rules/test/no-process-chdir/docs/index.md +11 -0
- package/rules/test/no-process-chdir/docs/main.md +32 -0
- package/rules/test/no-process-chdir/main.mjs +40 -0
- package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
- package/rules/test/no-relative-fs-path/concern.json +7 -0
- package/rules/test/no-relative-fs-path/docs/index.md +11 -0
- package/rules/test/no-relative-fs-path/docs/main.md +34 -0
- package/rules/test/no-relative-fs-path/main.mjs +239 -0
- package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
- package/rules/test/package_json/concern.json +11 -0
- package/rules/test/package_json/package_json.mdc +18 -0
- package/rules/test/package_json/package_json.rego +25 -0
- package/rules/test/package_json/template/package.json.contains.json +6 -0
- package/rules/test/sandbox-aware-test/concern.json +7 -0
- package/rules/test/sandbox-aware-test/docs/index.md +11 -0
- package/rules/test/sandbox-aware-test/docs/main.md +55 -0
- package/rules/test/sandbox-aware-test/main.mjs +89 -0
- package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
- package/rules/test/stryker_config/concern.json +8 -0
- package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
- package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
- package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
- package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
- package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
- package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
- package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
- package/rules/test/stryker_config/docs/fix-stryker_config.md +41 -0
- package/rules/test/stryker_config/docs/index.md +12 -0
- package/rules/test/stryker_config/docs/main.md +38 -0
- package/rules/test/stryker_config/fix-stryker_config.mjs +77 -0
- package/rules/test/stryker_config/main.mjs +504 -0
- package/rules/test/stryker_config/stryker_config.mdc +26 -0
- package/rules/test/vitest-api-conventions/concern.json +7 -0
- package/rules/test/vitest-api-conventions/docs/index.md +9 -0
- package/rules/test/vitest-api-conventions/docs/main.md +40 -0
- package/rules/test/vitest-api-conventions/main.mjs +153 -0
- package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
- package/rules/test/vitest-config-pool-forks/concern.json +7 -0
- package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
- package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
- package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
- 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.
|
|
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
|
-
Імпорт із `'
|
|
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. Імпортувати з `'
|
|
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/')`.
|
|
@@ -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('
|
|
17
|
-
* @returns {Promise<import('
|
|
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)
|
package/rules/style/gap/main.mjs
CHANGED
|
@@ -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('
|
|
17
|
-
* @returns {Promise<import('
|
|
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)
|
|
@@ -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('
|
|
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).
|
|
4
|
-
*
|
|
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('
|
|
37
|
-
* @returns {Promise<import('
|
|
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
|
-
|
|
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 })
|
|
@@ -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('
|
|
25
|
-
* @returns {Promise<import('
|
|
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: main.mjs
|
|
4
4
|
resource: plugins/lang-js/rules/style/tooling/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
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('
|
|
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('
|
|
49
|
-
* @returns {Promise<import('
|
|
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,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,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,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 |
|