@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
|
@@ -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,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 |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/no-console-store-restore/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 747e7e48
|
|
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
|
+
Перевіряє відповідність тестової логіки стандарту, забобонячи пряме присвоєння методів об'єкта `console` у JS-тестах. Забезпечує використання рекомендованого механізму мокування (`vi.spyOn.mockReturnValue`) для збереження цілісності тестового середовища.
|
|
16
|
+
|
|
17
|
+
Поведінка:
|
|
18
|
+
main запускає перевірку вказаного кореня репозиторію.
|
|
19
|
+
Збирається список усіх файлів, що відповідають шаблону JS-тестів.
|
|
20
|
+
Для кожного знайденого тестового файлу зчитується його вміст.
|
|
21
|
+
Перевіряється наявність прямого присвоєння методів об'єкта `console` (наприклад, `console.log = ...`).
|
|
22
|
+
Якщо порушень не знайдено, виводиться успішне повідомлення згідно з (test.mdc).
|
|
23
|
+
У разі виявлення порушення (пряме присвоєння), фіксується місце інциденту (файл та рядок) і генерується повідомлення про помилку, яке рекомендує замінити присвоєння на `vi.spyOn.mockReturnValue`, відповідно до (test.mdc, no-console-store-restore).
|
|
24
|
+
Виводиться відповідний код завершення.
|
|
25
|
+
|
|
26
|
+
## Поведінка
|
|
27
|
+
|
|
28
|
+
Поведінка:
|
|
29
|
+
|
|
30
|
+
1. main викликається для запуску перевірки вказаного кореня репозиторію.
|
|
31
|
+
2. Збирається список усіх файлів, що відповідають шаблону JS-тестів.
|
|
32
|
+
3. Для кожного знайденого тестового файлу зчитується його вміст.
|
|
33
|
+
4. Перевіряється, чи містить вміст пряме присвоєння методів об'єкта `console` (наприклад, `console.log = ...`).
|
|
34
|
+
5. Якщо жодного такого присвоєння не знайдено у жодному тестовому файлі, система повідомляє про успіх згідно з (test.mdc).
|
|
35
|
+
6. Якщо знайдено порушення (пряме присвоєння), система фіксує місце порушення (файл та рядок) і повідомляє про помилку, рекомендуючи замість цього використовувати `vi.spyOn.mockReturnValue` згідно з (test.mdc, no-console-store-restore).
|
|
36
|
+
7. Виводиться відповідний код завершення.
|
|
37
|
+
|
|
38
|
+
## Публічний API
|
|
39
|
+
|
|
40
|
+
main — перевіряє, що файли тесту не перевизначають консольні методи через пряме присвоєння, вимагаючи використання `vi.spyOn` для мокування.
|
|
41
|
+
|
|
42
|
+
## Гарантії поведінки
|
|
43
|
+
|
|
44
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @see ./docs/no-console-store-restore.md */
|
|
2
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
3
|
+
import { collectTestFileOffenders } from '../lib/collect-test-file-offenders.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ловить пряме присвоєння `console.<method> = …` у `*.test.{js,mjs}`.
|
|
7
|
+
* `console.log = fn` — process-wide мутація; канон: `vi.spyOn(console, 'log')`.
|
|
8
|
+
* `(?!=)` виключає `==` та `===` (лише одиночний `=`).
|
|
9
|
+
*/
|
|
10
|
+
const CONSOLE_ASSIGN_RE =
|
|
11
|
+
/\bconsole\.(?:log|error|warn|info|debug|dir|table|trace|group|groupEnd|time|timeEnd)\s*=(?!=)/u
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Знаходить рядки з прямим присвоєнням `console.<method> = …`.
|
|
15
|
+
* @param {string} body вміст файлу
|
|
16
|
+
* @returns {Array<{line: number}>} знайдені порушення
|
|
17
|
+
*/
|
|
18
|
+
function findOffenders(body) {
|
|
19
|
+
const offenders = []
|
|
20
|
+
const lines = body.split('\n')
|
|
21
|
+
for (const [i, line] of lines.entries()) {
|
|
22
|
+
if (CONSOLE_ASSIGN_RE.test(line)) {
|
|
23
|
+
offenders.push({ line: i + 1 })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return offenders
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Перевіряє, що жоден `*.test.{mjs,js}` файл не перевизначає `console.<method>`
|
|
31
|
+
* через пряме присвоєння. Канон — `vi.spyOn(console, 'log').mockReturnValue()`.
|
|
32
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
33
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки з порушеннями.
|
|
34
|
+
*/
|
|
35
|
+
export async function lint(ctx) {
|
|
36
|
+
const reporter = createViolationReporter(ctx)
|
|
37
|
+
const { pass, fail } = reporter
|
|
38
|
+
|
|
39
|
+
const cwd = ctx.cwd
|
|
40
|
+
const { testFiles, offenders } = await collectTestFileOffenders(cwd, findOffenders)
|
|
41
|
+
|
|
42
|
+
if (offenders.length === 0) {
|
|
43
|
+
pass(`Жоден з ${testFiles.length} тестових файлів не присвоює console.<method> = … (test.mdc)`)
|
|
44
|
+
return reporter.result()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const { file, line } of offenders) {
|
|
48
|
+
fail(
|
|
49
|
+
`${file}:${line}: пряме присвоєння console.<method> = … заборонено — ` +
|
|
50
|
+
`використовуй vi.spyOn(console, 'method').mockReturnValue() (test.mdc, no-console-store-restore)`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return reporter.result()
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Заборона ручного store/restore `console` у тестах
|
|
2
|
+
|
|
3
|
+
`console.log` / `console.error` / `console.warn` — **process-wide** мутації, рівно як `process.cwd()`. Якщо тест перехоплює їх через `const orig = console.log; console.log = (...) => …; try { … } finally { console.log = orig }`, паралельний test файл у `pool: 'forks'` (різний процес) ізольований, але в межах **одного** процесу всі тести у файлі ділять єдиний `console`-об'єкт. Якщо try/finally не виконається (наприклад, асинхронна помилка повз `await`), restore не спрацює і наступні тести втрачають вивід.
|
|
4
|
+
|
|
5
|
+
Тому:
|
|
6
|
+
|
|
7
|
+
- **Канон**: `vi.spyOn(console, 'log').mockReturnValue()` (та аналоги для `error`/`warn`/`info`) + `afterEach(() => vi.restoreAllMocks())`. Vitest сам слідкує за scope mock-у і гарантовано відновлює оригінал між тестами, навіть якщо тест кинув виняток.
|
|
8
|
+
- **Заборонено**: ручний store/restore (`const orig = console.log; console.log = stub`). Виняток — коли тест **необхідно** перехопити вивід **до** того, як завантажиться модуль із top-level-логуванням; у цьому випадку фіксуй pattern explicit-коментарем.
|
|
9
|
+
- Якщо потрібен лог зі stub-ом — `const logs = []; vi.spyOn(console, 'log').mockImplementation((...args) => logs.push(args.join(' ')))`.
|
|
10
|
+
|
|
11
|
+
**Перевірка** — концерн `no-console-store-restore` (`rules/test/js/no-console-store-restore.mjs`): AST-сканер, який ловить присвоєння `console.<method> = …` у `*.test.{js,mjs}`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/test/no-process-chdir
|
|
4
|
+
resource: plugins/lang-js/rules/test/no-process-chdir/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/rules/test/no-process-chdir
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------- | --------- |
|
|
11
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/no-process-chdir/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 66ca3322
|
|
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
|
+
Поведінка програми полягає у виконанні основної логіки, визначеної в функції `main`. Програма надає функціональність, що не передбачає взаємодії з файловою системою або базами даних. Для верифікації коректності роботи логіки необхідно застосувати тести, які відносяться до `test.mdc`.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. Викликається `main` для початку перевірки.
|
|
20
|
+
2. Програма шукає всі файли, що відповідають шаблону JS-тесту.
|
|
21
|
+
3. Для кожного тестового файлу здійснюється пошук виклику `process.chdir`.
|
|
22
|
+
4. Якщо виклик знайдено у тестовому файлі, фіксується його місцезнаходження.
|
|
23
|
+
5. Якщо жоден виклик не знайдено, виводиться повідомлення про успішне виконання перевірки (test.mdc).
|
|
24
|
+
6. Якщо знайдено виклики, для кожного інциденту виводиться повідомлення про порушення (test.mdc), з обов'язковою згадкою про рекомендацію використання `withTmpDir + явні join + cwd: dir`.
|
|
25
|
+
|
|
26
|
+
## Публічний API
|
|
27
|
+
|
|
28
|
+
main — Забороняє зміну робочої директорії (`process.chdir`) у тестових файлах.
|
|
29
|
+
|
|
30
|
+
## Гарантії поведінки
|
|
31
|
+
|
|
32
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** @see ./docs/no-process-chdir.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { collectTestFiles, toRelPosix } from '@7n/rules/scripts/lib/collect-test-files.mjs'
|
|
5
|
+
|
|
6
|
+
/** Шукаємо викликний паттерн з відкривною дужкою — не зачепить згадку у docstring. */
|
|
7
|
+
const CHDIR_CALL_RE = /process\.chdir\s*\(/u
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detector: жоден `*.test.{mjs,js}` не викликає `process.chdir(` (test.mdc).
|
|
11
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx Контекст лінту (`cwd` тощо).
|
|
12
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} Результат лінту зі списком violations.
|
|
13
|
+
*/
|
|
14
|
+
export async function lint(ctx) {
|
|
15
|
+
const { cwd } = ctx
|
|
16
|
+
const testFiles = await collectTestFiles(cwd)
|
|
17
|
+
|
|
18
|
+
/** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintViolation[]} */
|
|
19
|
+
const violations = []
|
|
20
|
+
for (const absPath of testFiles) {
|
|
21
|
+
const body = await readFile(absPath, 'utf8')
|
|
22
|
+
if (!CHDIR_CALL_RE.test(body)) continue
|
|
23
|
+
const file = toRelPosix(cwd, absPath)
|
|
24
|
+
for (const [i, line] of body.split('\n').entries()) {
|
|
25
|
+
if (!CHDIR_CALL_RE.test(line)) continue
|
|
26
|
+
violations.push(
|
|
27
|
+
/** @type {Partial<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintViolation>} */ ({
|
|
28
|
+
reason: 'process-chdir-in-test',
|
|
29
|
+
message:
|
|
30
|
+
`${file}:${i + 1}: process.chdir() у тесті заборонений — використовуй ` +
|
|
31
|
+
'withTmpDir(async dir => …) + явні join(dir, …) + cwd: dir (test.mdc)',
|
|
32
|
+
file,
|
|
33
|
+
data: { line: i + 1 }
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { violations }
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Заборона `process.chdir` у тестах
|
|
2
|
+
|
|
3
|
+
`process.chdir(dir)` — **process-wide** мутація. Vitest за замовчуванням ставить `pool: 'threads'`, і всі workers ділять один процес: паралельний test file може перехопити cwd сусіда посеред FS- або `git`-операції. У реальному інциденті це призвело до того, що `git init`+`git commit` із tmp-фікстури потрапив у реальний робочий репозиторій і створив rogue commit з автором `test <test@test>`, знищивши `npm/package.json` / `CHANGELOG.md`.
|
|
4
|
+
|
|
5
|
+
Тому:
|
|
6
|
+
|
|
7
|
+
- **У тестах заборонено** `process.chdir(...)` напряму та будь-які хелпери, що його викликають (історичний `withTmpCwd` видалений у `1.28.0`).
|
|
8
|
+
- Канон: `withTmpDir(async dir => { ... })` зі `scripts/utils/test-helpers.mjs` — створює tmpdir і передає **абсолютний** `dir` у callback **без** `process.chdir`.
|
|
9
|
+
- Усі FS-операції у тесті — через `join(dir, …)` і `writeJson(join(dir, …), …)` / `ensureDir(join(dir, …))` (хелпери валідують `isAbsolute`).
|
|
10
|
+
- Усі child-процеси — `execFile(bin, args, { cwd: dir })`, `spawnSync(bin, args, { cwd: dir })`.
|
|
11
|
+
- Concern-функції правил — `await check(dir)`, `await applies(dir)`, `await fix(dir)`; усі production функції приймають перший параметр `cwd = process.cwd()` (default зберігає CLI-сумісність).
|
|
12
|
+
|
|
13
|
+
**Перевірка** (`rules/test/js/no-process-chdir.mjs`): сканує `**/*.test.{js,mjs}` і падає з ❌ на будь-яке вживання `process.chdir(`.
|
|
14
|
+
|
|
15
|
+
**Кожен виклик `execFile`/`execFileSync`/`spawn`/`spawnSync`** у `*.test.{js,mjs}` приймає або **явний `{ cwd: dir }`** (де `dir` — змінна, не `process.cwd()`), або працює з **абсолютними шляхами** до бінарників/fixture-файлів. Це гарантує, що мутаційний test-flow у `pool: 'forks'` не страждає від implicit-`process.cwd()`-stride між тестами в одному воркері.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/test/no-relative-fs-path
|
|
4
|
+
resource: plugins/lang-js/rules/test/no-relative-fs-path/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/rules/test/no-relative-fs-path
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------- | --------- |
|
|
11
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/no-relative-fs-path/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: b8f4b7aa
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 95
|
|
9
|
+
issues: anchor-miss:(test.mdc),judge:inaccurate:0.99
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
main функція забезпечує виконання основного логічного блоку програми. Вона відповідає за ініціалізацію системи та керує її життєвим циклом. Кешування даних здійснюється протягом одного виконання для підвищення продуктивності за рахунок уникнення повторних обчислень. Поведінка системи включає інтеграцію маркерів повідомлень відповідно до правил `test.mdc`.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. Викликати `main` для запуску перевірки.
|
|
20
|
+
2. Система збирає список усіх файлів, що є тестовими (завершуються на `*.test.mjs` або `*.test.js`) у директорії проєкту та її підкаталогах.
|
|
21
|
+
3. Для кожного знайденого тестового файлу система зчитує його вміст.
|
|
22
|
+
4. Система аналізує вміст кожного тестового файлу, шукаючи виклики функцій з модулю `node:fs` або `node:fs/promises`, які приймають шляхи.
|
|
23
|
+
5. При виявленні такого виклику система перевіряє, чи використовується як літеральний шлях (string literal або template literal без виразів) відносний шлях.
|
|
24
|
+
6. Якщо відносний шлях знайдено, система фіксує місце порушення: ім'я файлу, номер рядка, назву функції та позицію аргументу.
|
|
25
|
+
7. Якщо порушень не знайдено у жодному тестовому файлі, виконується фіксація успішного завершення перевірки.
|
|
26
|
+
8. Якщо порушення знайдено, для кожного знайденого випадку система фіксує помилку, вказуючи, що використовується відносний шлях і що слід замінити його на обчислення за допомогою `join`.
|
|
27
|
+
|
|
28
|
+
## Публічний API
|
|
29
|
+
|
|
30
|
+
main — Гарантує, що тестові файли не використовують відносні шляхи як перший аргумент при виклику файлових операцій з `node:fs` або `node:fs/promises`.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Кешує результати в межах одного прогону.
|