@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,239 @@
|
|
|
1
|
+
/** @see ./docs/no-relative-fs-path.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, relative } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
6
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
7
|
+
import { parseProgramOrNull, walkAstWithAncestors } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
|
|
8
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* FS-функції з `node:fs` / `node:fs/promises` / sync-API, які приймають path
|
|
12
|
+
* у фіксованих позиціях. Map: ім'я функції → масив 0-індексованих позицій
|
|
13
|
+
* path-аргументів (1-й, 2-й, або обидва — як у `copyFile/rename/symlink/link`).
|
|
14
|
+
*/
|
|
15
|
+
const FS_PATH_ARG_POSITIONS = new Map([
|
|
16
|
+
['writeFile', [0]],
|
|
17
|
+
['writeFileSync', [0]],
|
|
18
|
+
['readFile', [0]],
|
|
19
|
+
['readFileSync', [0]],
|
|
20
|
+
['appendFile', [0]],
|
|
21
|
+
['appendFileSync', [0]],
|
|
22
|
+
['mkdir', [0]],
|
|
23
|
+
['mkdirSync', [0]],
|
|
24
|
+
['rmdir', [0]],
|
|
25
|
+
['rmdirSync', [0]],
|
|
26
|
+
['rm', [0]],
|
|
27
|
+
['rmSync', [0]],
|
|
28
|
+
['unlink', [0]],
|
|
29
|
+
['unlinkSync', [0]],
|
|
30
|
+
['access', [0]],
|
|
31
|
+
['accessSync', [0]],
|
|
32
|
+
['stat', [0]],
|
|
33
|
+
['statSync', [0]],
|
|
34
|
+
['lstat', [0]],
|
|
35
|
+
['lstatSync', [0]],
|
|
36
|
+
['chmod', [0]],
|
|
37
|
+
['chmodSync', [0]],
|
|
38
|
+
['chown', [0]],
|
|
39
|
+
['chownSync', [0]],
|
|
40
|
+
['truncate', [0]],
|
|
41
|
+
['truncateSync', [0]],
|
|
42
|
+
['existsSync', [0]],
|
|
43
|
+
['readdir', [0]],
|
|
44
|
+
['readdirSync', [0]],
|
|
45
|
+
['copyFile', [0, 1]],
|
|
46
|
+
['copyFileSync', [0, 1]],
|
|
47
|
+
['rename', [0, 1]],
|
|
48
|
+
['renameSync', [0, 1]],
|
|
49
|
+
['symlink', [0, 1]],
|
|
50
|
+
['symlinkSync', [0, 1]],
|
|
51
|
+
['link', [0, 1]],
|
|
52
|
+
['linkSync', [0, 1]],
|
|
53
|
+
['cp', [0, 1]],
|
|
54
|
+
['cpSync', [0, 1]],
|
|
55
|
+
// test-helpers абсолютні-only форми (зайвий захист)
|
|
56
|
+
['writeJson', [0]],
|
|
57
|
+
['ensureDir', [0]]
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Префікси абсолютних шляхів або очевидно-обчислених. Якщо literal починається з
|
|
62
|
+
* одного з них — це OK (тест свідомо передає absolute чи URL).
|
|
63
|
+
*/
|
|
64
|
+
const ABSOLUTE_PREFIXES = ['/', '\\', 'file:', 'http:', 'https:', 'data:']
|
|
65
|
+
const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/u
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Чи string literal — relative path (тобто баг). Перевіряє лише string-літерали
|
|
69
|
+
* та template literals без виразів. Виклики `join(...)` / `resolve(...)` /
|
|
70
|
+
* перемінні з ${} — пропускаємо (припускаємо absolute).
|
|
71
|
+
* @param {object} arg AST node аргументу
|
|
72
|
+
* @returns {string|null} relative-path значення (для меседжа), або null якщо OK
|
|
73
|
+
*/
|
|
74
|
+
function extractRelativeLiteralPath(arg) {
|
|
75
|
+
if (!arg) return null
|
|
76
|
+
if (arg.type === 'Literal' && typeof arg.value === 'string') {
|
|
77
|
+
return isRelativeString(arg.value) ? arg.value : null
|
|
78
|
+
}
|
|
79
|
+
if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) {
|
|
80
|
+
const raw = arg.quasis.map(q => q.value.cooked).join('')
|
|
81
|
+
return isRelativeString(raw) ? raw : null
|
|
82
|
+
}
|
|
83
|
+
// Не string-literal — не аналізуємо (припускаємо обчислений absolute через join/resolve).
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Чи рядок виглядає як relative path. Порожній рядок — false (це не path).
|
|
89
|
+
* Windows-disk-letter `C:\…` — absolute, бо містить `:` між літерою і `\`.
|
|
90
|
+
* @param {string} s рядок-шлях
|
|
91
|
+
* @returns {boolean} true якщо relative
|
|
92
|
+
*/
|
|
93
|
+
function isRelativeString(s) {
|
|
94
|
+
if (!s) return false
|
|
95
|
+
for (const prefix of ABSOLUTE_PREFIXES) {
|
|
96
|
+
if (s.startsWith(prefix)) return false
|
|
97
|
+
}
|
|
98
|
+
// Windows drive letter, наприклад `C:\foo` або `C:/foo`.
|
|
99
|
+
return !WINDOWS_DRIVE_RE.test(s)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Витягує ім'я FS-функції з callee:
|
|
104
|
+
* - `writeFile(…)` → "writeFile" (Identifier callee)
|
|
105
|
+
* - `fs.writeFile(…)` чи `fsp.writeFile(…)` → "writeFile" (MemberExpression)
|
|
106
|
+
* - `await fs.promises.writeFile(…)` → "writeFile"
|
|
107
|
+
* Повертає null для будь-якого іншого виклику.
|
|
108
|
+
* @param {object} callee AST callee node
|
|
109
|
+
* @returns {string|null} ім'я FS-функції або null
|
|
110
|
+
*/
|
|
111
|
+
function extractFsFunctionName(callee) {
|
|
112
|
+
if (!callee) return null
|
|
113
|
+
if (callee.type === 'Identifier') {
|
|
114
|
+
return FS_PATH_ARG_POSITIONS.has(callee.name) ? callee.name : null
|
|
115
|
+
}
|
|
116
|
+
if (callee.type === 'MemberExpression' && !callee.computed && callee.property?.type === 'Identifier') {
|
|
117
|
+
const name = callee.property.name
|
|
118
|
+
return FS_PATH_ARG_POSITIONS.has(name) ? name : null
|
|
119
|
+
}
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Чи файл — JS-тест (`*.test.mjs` / `*.test.js`).
|
|
125
|
+
* @param {string} absPath абсолютний шлях
|
|
126
|
+
* @returns {boolean} true якщо файл є тестом
|
|
127
|
+
*/
|
|
128
|
+
function isTestFile(absPath) {
|
|
129
|
+
const name = basename(absPath)
|
|
130
|
+
return name.endsWith('.test.mjs') || name.endsWith('.test.js')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Знаходить порушення у одному тестовому файлі.
|
|
135
|
+
* @param {string} body вміст тесту
|
|
136
|
+
* @returns {Array<{line: number, fn: string, path: string, argPos: number}>} порушення
|
|
137
|
+
*/
|
|
138
|
+
function findOffendersInBody(body) {
|
|
139
|
+
const program = parseProgramOrNull(body, 'test.mjs')
|
|
140
|
+
if (!program) return []
|
|
141
|
+
const offenders = []
|
|
142
|
+
const lineOffsets = computeLineOffsets(body)
|
|
143
|
+
walkAstWithAncestors(program, [], node => {
|
|
144
|
+
if (node?.type !== 'CallExpression') return
|
|
145
|
+
const fnName = extractFsFunctionName(node.callee)
|
|
146
|
+
if (!fnName) return
|
|
147
|
+
const positions = FS_PATH_ARG_POSITIONS.get(fnName)
|
|
148
|
+
for (const pos of positions) {
|
|
149
|
+
const arg = node.arguments?.[pos]
|
|
150
|
+
const relPath = extractRelativeLiteralPath(arg)
|
|
151
|
+
if (relPath !== null) {
|
|
152
|
+
const start = arg?.start ?? node.start ?? 0
|
|
153
|
+
const line = offsetToLineFromCache(lineOffsets, start)
|
|
154
|
+
offenders.push({ line, fn: fnName, path: relPath, argPos: pos })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
return offenders
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Кешований offset→line: бінарний пошук по newline-offsets.
|
|
163
|
+
* @param {string} body source
|
|
164
|
+
* @returns {number[]} offsets newline char positions
|
|
165
|
+
*/
|
|
166
|
+
function computeLineOffsets(body) {
|
|
167
|
+
const offsets = [0]
|
|
168
|
+
let pos = 0
|
|
169
|
+
for (const ch of body) {
|
|
170
|
+
if (ch === '\n') offsets.push(pos + 1)
|
|
171
|
+
pos += 1
|
|
172
|
+
}
|
|
173
|
+
return offsets
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {number[]} offsets newline-offsets
|
|
178
|
+
* @param {number} offset 0-індекс символу
|
|
179
|
+
* @returns {number} 1-індексований рядок
|
|
180
|
+
*/
|
|
181
|
+
function offsetToLineFromCache(offsets, offset) {
|
|
182
|
+
let lo = 0
|
|
183
|
+
let hi = offsets.length - 1
|
|
184
|
+
while (lo < hi) {
|
|
185
|
+
const mid = Math.floor((lo + hi + 1) / 2)
|
|
186
|
+
if (offsets[mid] <= offset) lo = mid
|
|
187
|
+
else hi = mid - 1
|
|
188
|
+
}
|
|
189
|
+
return lo + 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Перевіряє, що жоден `*.test.{mjs,js}` файл не передає relative-path як 1-й
|
|
194
|
+
* (або для `copyFile`/`rename`/`symlink`/`link`/`cp` — 1-й і 2-й) аргумент
|
|
195
|
+
* у FS-функцію з `node:fs` / `node:fs/promises`.
|
|
196
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx Контекст лінту (cwd, перелік файлів тощо).
|
|
197
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} Результат лінту з переліком порушень.
|
|
198
|
+
*/
|
|
199
|
+
export async function lint(ctx) {
|
|
200
|
+
const reporter = createViolationReporter(ctx)
|
|
201
|
+
const { pass, fail } = reporter
|
|
202
|
+
|
|
203
|
+
const cwd = ctx.cwd
|
|
204
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
205
|
+
|
|
206
|
+
/** @type {string[]} */
|
|
207
|
+
const testFiles = []
|
|
208
|
+
await walkDir(
|
|
209
|
+
cwd,
|
|
210
|
+
absPath => {
|
|
211
|
+
if (isTestFile(absPath)) testFiles.push(absPath)
|
|
212
|
+
},
|
|
213
|
+
ignorePaths
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
/** @type {Array<{file: string, line: number, fn: string, path: string, argPos: number}>} */
|
|
217
|
+
const offenders = []
|
|
218
|
+
for (const absPath of testFiles) {
|
|
219
|
+
const body = await readFile(absPath, 'utf8')
|
|
220
|
+
const found = findOffendersInBody(body)
|
|
221
|
+
for (const o of found) {
|
|
222
|
+
offenders.push({ file: relative(cwd, absPath), ...o })
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (offenders.length === 0) {
|
|
227
|
+
pass(`Жоден з ${testFiles.length} тестових файлів не передає relative-path у FS-функції (test.mdc)`)
|
|
228
|
+
return reporter.result()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const { file, line, fn, path, argPos } of offenders) {
|
|
232
|
+
const which = argPos === 0 ? '1-й аргумент' : `${argPos + 1}-й аргумент`
|
|
233
|
+
fail(
|
|
234
|
+
`${file}:${line}: ${fn}() — ${which} '${path}' relative; використовуй join(dir, …) (test.mdc, no-relative-fs-path)`
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return reporter.result()
|
|
239
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Заборона відносних шляхів у FS-функціях тестів
|
|
2
|
+
|
|
3
|
+
**`no-relative-fs-path`** (`rules/test/js/no-relative-fs-path.mjs`) — AST-сканер (`oxc-parser`): знаходить виклики FS-функцій із `node:fs`/`node:fs/promises` (`writeFile`, `copyFile`, `mkdir`, `readFile`, `existsSync`, `rename`, `symlink`, `cp`, … включно з `*Sync`-варіантами та `writeJson`/`ensureDir`-хелперами), де path-аргумент — це **string literal** без префікса `/`, `\`, `file:`, `http(s):`, `data:`, чи Windows-disk-letter `C:\`.
|
|
4
|
+
|
|
5
|
+
Виклики `copyFile`/`rename`/`symlink`/`link`/`cp` перевіряють **обидва** path-аргументи.
|
|
6
|
+
|
|
7
|
+
Виклики з обчисленим path (`join(dir, …)`, змінна, template-literal з виразом) пропускаються — припускається, що обчислений шлях абсолютний.
|
|
8
|
+
|
|
9
|
+
**Чому важливо:** відносний path у FS-виклику означає запис/читання відносно `process.cwd()` на момент виклику. У `pool: 'threads'` (дефолт Vitest) паралельні файли ділять процес — cwd може змінитися між тестами. Інцидент `v1.28.0`: `copyFile(src, 'default.conf.template')` у `tests/check-rule-fixtures.test.mjs` записав файл у production tree замість sandbox.
|
|
10
|
+
|
|
11
|
+
**Канон:** замінювати відносні шляхи на `join(dir, 'filename')`, де `dir` — абсолютний tmpdir із `withTmpDir`.
|
|
12
|
+
|
|
13
|
+
**Список FS-функцій, що перевіряються:**
|
|
14
|
+
|
|
15
|
+
| Функція | Перевірювані аргументи |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `writeFile`, `readFile`, `appendFile`, `mkdir`, `rmdir`, `rm`, `unlink` | 1-й |
|
|
18
|
+
| `access`, `stat`, `lstat`, `chmod`, `chown`, `truncate`, `existsSync`, `readdir` | 1-й |
|
|
19
|
+
| `copyFile`, `rename`, `symlink`, `link`, `cp` | 1-й і 2-й |
|
|
20
|
+
| `writeJson`, `ensureDir` (test-helpers) | 1-й |
|
|
21
|
+
|
|
22
|
+
Усі `*Sync`-варіанти охоплені аналогічно.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
+
"fixability": "config",
|
|
4
|
+
"policy": {
|
|
5
|
+
"files": {
|
|
6
|
+
"single": "package.json",
|
|
7
|
+
"required": true
|
|
8
|
+
},
|
|
9
|
+
"missingMessage": "package.json не існує — створи зі scripts.coverage (test.mdc)"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Обовʼязкові скрипти в package.json
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `test.package_json`
|
|
4
|
+
|
|
5
|
+
Цільовий файл: `package.json` кожного workspace.
|
|
6
|
+
|
|
7
|
+
Перевіряє substring-відповідність значень у `scripts`:
|
|
8
|
+
|
|
9
|
+
| Поле | Має містити |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `scripts.coverage` | `@7n/test coverage` |
|
|
12
|
+
| `scripts.test` | `vitest` і `--bun` (напр. `"bun run --bun vitest run"`) |
|
|
13
|
+
|
|
14
|
+
Substring-семантика: команди-обгортки (наприклад `bun run pre-coverage && npx @7n/test coverage`, `bun run pre-test && bun run --bun vitest run`) дозволені — головне, щоб потрібні рядки були присутні.
|
|
15
|
+
|
|
16
|
+
**Чому `--bun` для `scripts.test`:** без Bun-рушія (`bun run vitest` без `--bun`, чи vitest під Node) `import { SQL } from 'bun'` та інші Bun-нативні built-in модулі не резолвуються у forked test-процесах — консьюмери змушені тримати exclude-списки для тестів, що їх торкаються. Під `bun run --bun vitest run` forked pool-процеси успадковують Bun-рушій і резолюція працює без жодних exclude.
|
|
17
|
+
|
|
18
|
+
Канон підтягується через `--data`: [package.json.contains.json](./template/package.json.contains.json)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Перевірка `package.json` для правила test (test.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.contains.json.
|
|
5
|
+
# Перевіряємо substring-вимоги до scripts.coverage і scripts.test:
|
|
6
|
+
# рядки мають містити відповідно "@7n/test coverage" і "vitest" + "--bun".
|
|
7
|
+
package test.package_json
|
|
8
|
+
|
|
9
|
+
import rego.v1
|
|
10
|
+
|
|
11
|
+
deny contains msg if {
|
|
12
|
+
some script_name, needles in data.template.contains.scripts
|
|
13
|
+
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
14
|
+
some needle in needles
|
|
15
|
+
not contains(actual, needle)
|
|
16
|
+
msg := sprintf("package.json: scripts.%s має містити %q (test.mdc)%s", [script_name, needle, explain_suffix(needle)])
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# --bun пояснюється окремо: без нього forked vitest pool-процеси не успадковують
|
|
20
|
+
# Bun-рушій, і Bun-нативні built-in модулі (напр. `import { SQL } from 'bun'`)
|
|
21
|
+
# не резолвуються у forked test-процесах.
|
|
22
|
+
explain_suffix(needle) := msg if {
|
|
23
|
+
needle == "--bun"
|
|
24
|
+
msg := " — без --bun forked vitest pool-процеси не успадковують Bun-рушій, тож Bun-нативні built-in модулі (напр. import { SQL } from 'bun') не резолвуються у forked test-процесах"
|
|
25
|
+
} else := ""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/test/sandbox-aware-test
|
|
4
|
+
resource: plugins/lang-js/rules/test/sandbox-aware-test/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/rules/test/sandbox-aware-test
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------- | --------- |
|
|
11
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/sandbox-aware-test/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: e903e8ef
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 95
|
|
9
|
+
issues: anchor-miss:(test.mdc),judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей файл сканує репозиторій для пошуку тестових файлів, що відповідають шаблону JS-тесту. Він аналізує глибину імпорту в цих тестах, перевіряючи наявність механізмів ізоляції, таких як `withTmpDir` або `test.skipIf`. Це гарантує, що тести, які глибоко імпортуються у структуру проєкту, будуть належним чином ізольовані. Маркери повідомлень, пов'язані з тестами, відображаються в Поведінці.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
Поведінка:
|
|
20
|
+
|
|
21
|
+
1. Викликається `main`.
|
|
22
|
+
2. Сканується репозиторій для пошуку файлів, що відповідають шаблону JS-тесту.
|
|
23
|
+
3. Для кожного знайденого тестового файлу аналізується його вміст на наявність глибокої навігації за допомогою `import.meta.dirname` або `import.meta.url`, яка вказує на глибину не менше чотирьох рівнів підкаталогів.
|
|
24
|
+
4. Якщо в тестовому файлі виявлено глибоку навігацію, перевіряється, чи цей файл захищений викликом `withTmpDir` або чи містить конструкцію `test.skipIf` для ізоляції у Stryker-sandbox.
|
|
25
|
+
5. Якщо глибока навігація знайдена, а захист відсутній, файлу позначається як порушення.
|
|
26
|
+
6. При відсутності порушень у всіх тестових файлах, процес вважається успішним.
|
|
27
|
+
7. При виявленні порушень, генерується повідомлення з вказівкою виправлення (використання `withTmpDir` або захист через `test.skipIf`), з посиланням на `test.mdc`.
|
|
28
|
+
|
|
29
|
+
## Публічний API
|
|
30
|
+
|
|
31
|
+
Task: Rewrite a technical description into concise, behavioral documentation snippets based on provided rules.
|
|
32
|
+
|
|
33
|
+
Goal: Produce laconic, behavioral documentation in Ukrainian using clean Markdown, focusing on _What_ and _Why_, not _How_.
|
|
34
|
+
|
|
35
|
+
Constraints:
|
|
36
|
+
|
|
37
|
+
1. No introductions or conclusions.
|
|
38
|
+
2. Do not wrap in ```-code blocks.
|
|
39
|
+
3. Forbidden: function signatures, types, parameters list, stdlib module descriptions, regex descriptions, or internal private names.
|
|
40
|
+
4. Mandatory Anchors: Mention message markers in Behavior (per `test.mdc`).
|
|
41
|
+
5. Required Format: Concise markers in the format "name — what it does," using your own words (no direct copying), without types or signatures.
|
|
42
|
+
6. Exact Names: Must use the provided names exactly.
|
|
43
|
+
7. Strict Omission: No header, no generic phrases ("applies logic," "checks correctness")—be specific about what is being applied/checked.
|
|
44
|
+
|
|
45
|
+
Input List to transform:
|
|
46
|
+
|
|
47
|
+
- main: Перевіряє, що `*.test.{mjs,js}` з глибокою `import.meta`-навігацією (≥4 `..`-рівнів) захищені `withTmpDir` або `test.skipIf`. Без ізоляції Stryker-sandbox (`reports/stryker/.tmp/sandbox-XXX/`) не має `.git/`, тому git-операції у таких тестах падають і мутаційний прогін не стартує. Без заголовка. Без generic-фраз «застосовує логіку», «перевіряє коректність» — пиши конкретно ЩО саме застосовує/перевіряє.
|
|
48
|
+
|
|
49
|
+
Applying the transformation now.
|
|
50
|
+
|
|
51
|
+
main — Встановлює захист для тестів з глибокою навігацією `import.meta` (≥4 `..`-рівнів), гарантуючи, що вони працюють у середовищі, де відсутня ізоляція Stryker-sandbox, що запобігає збоям git-операцій і старту процесу мутації.
|
|
52
|
+
|
|
53
|
+
## Гарантії поведінки
|
|
54
|
+
|
|
55
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** @see ./docs/sandbox-aware-test.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, relative } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
6
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
7
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Чи файл — JS-тест (`*.test.mjs` / `*.test.js`).
|
|
11
|
+
* @param {string} absPath абсолютний шлях
|
|
12
|
+
* @returns {boolean} `true` для `.test.{mjs,js}` файлів
|
|
13
|
+
*/
|
|
14
|
+
function isTestFile(absPath) {
|
|
15
|
+
const name = basename(absPath)
|
|
16
|
+
return name.endsWith('.test.mjs') || name.endsWith('.test.js')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Чи файл містить `import.meta.dirname`/`import.meta.url`-навігацію з ≥4 `..`-рівнів.
|
|
21
|
+
* Для кожного вживання `import.meta.dirname|url` рахує `'..'`/`".."` у вікні 400 символів.
|
|
22
|
+
* @param {string} body вміст файлу
|
|
23
|
+
* @returns {boolean} `true` якщо знайдено глибоку навігацію
|
|
24
|
+
*/
|
|
25
|
+
function hasDeepMetaNavigation(body) {
|
|
26
|
+
const RE = /import\.meta\.(?:dirname|url)\b/gu
|
|
27
|
+
let match
|
|
28
|
+
while ((match = RE.exec(body)) !== null) {
|
|
29
|
+
const chunk = body.slice(match.index, match.index + 400)
|
|
30
|
+
const dots = (chunk.match(/'\.\.'|"\.\."/gu) ?? []).length
|
|
31
|
+
if (dots >= 4) return true
|
|
32
|
+
}
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Захист через тимчасову пісочницю — `withTmpDir`. */
|
|
37
|
+
const WITH_TMP_DIR_RE = /\bwithTmpDir\b/u
|
|
38
|
+
|
|
39
|
+
/** Захист через явний skip у Stryker-sandbox (`test.skipIf`). */
|
|
40
|
+
const SKIP_IF_STRYKER_RE = /\btest\.skipIf\s*\(\s*(?:env|process\.env)\.STRYKER_MUTATOR_WORKER\b/u
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Перевіряє, що `*.test.{mjs,js}` з глибокою `import.meta`-навігацією (≥4 `..`-рівнів)
|
|
44
|
+
* захищені `withTmpDir` або `test.skipIf(env.STRYKER_MUTATOR_WORKER)`.
|
|
45
|
+
* Без ізоляції Stryker-sandbox (`reports/stryker/.tmp/sandbox-XXX/`) не має `.git/`,
|
|
46
|
+
* тому git-операції у таких тестах падають і мутаційний прогін не стартує.
|
|
47
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
|
|
48
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями.
|
|
49
|
+
*/
|
|
50
|
+
export async function lint(ctx) {
|
|
51
|
+
const reporter = createViolationReporter(ctx)
|
|
52
|
+
const { pass, fail } = reporter
|
|
53
|
+
|
|
54
|
+
const cwd = ctx.cwd
|
|
55
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
56
|
+
|
|
57
|
+
/** @type {string[]} */
|
|
58
|
+
const testFiles = []
|
|
59
|
+
await walkDir(
|
|
60
|
+
cwd,
|
|
61
|
+
absPath => {
|
|
62
|
+
if (isTestFile(absPath)) testFiles.push(absPath)
|
|
63
|
+
},
|
|
64
|
+
ignorePaths
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
/** @type {string[]} */
|
|
68
|
+
const offenders = []
|
|
69
|
+
for (const absPath of testFiles) {
|
|
70
|
+
const body = await readFile(absPath, 'utf8')
|
|
71
|
+
if (!hasDeepMetaNavigation(body)) continue
|
|
72
|
+
if (WITH_TMP_DIR_RE.test(body) || SKIP_IF_STRYKER_RE.test(body)) continue
|
|
73
|
+
offenders.push(relative(cwd, absPath))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (offenders.length === 0) {
|
|
77
|
+
pass(`Усі ${testFiles.length} тестові файли sandbox-aware (test.mdc)`)
|
|
78
|
+
return reporter.result()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const file of offenders) {
|
|
82
|
+
fail(
|
|
83
|
+
`${file}: import.meta deep navigation (≥4 рівні ..) без ізоляції — ` +
|
|
84
|
+
`оберни у withTmpDir() або захисти test.skipIf(env.STRYKER_MUTATOR_WORKER) (test.mdc, sandbox-aware-test)`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return reporter.result()
|
|
89
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Sandbox-aware тести (Stryker)
|
|
2
|
+
|
|
3
|
+
Stryker за замовчуванням копіює репо у sandbox-каталог (`reports/stryker/.tmp/sandbox-XXX/`), щоб AST-патчити мутантів без зачеплення робочого дерева. У sandbox **немає `.git/`**, і будь-який тест, що звертається до **реального** git-дерева через `import.meta.url + N≥3 рівнів вгору** (типу`const REPO_ROOT = join(import.meta.dirname, '..', '..', '..', '..', '..')`) фактично адресує sandbox-корінь, а не справжній репо. Це ламає`git rev-parse`/`git ls-files`/перевірки`.n-rules.json`/CHANGELOG-співставлення — Stryker dry-run падає, мутаційний прогон не стартує,`mutation.json` лишається stale.
|
|
4
|
+
|
|
5
|
+
**Канон**: тест не повинен залежати від реального git-дерева через `import.meta.url + N≥3 рівнів вгору`. Якщо тест перевіряє git-логіку — ізолюй **повністю**:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
await withTmpDir(async dir => {
|
|
9
|
+
execFileSync('git', ['init', '-q', '--initial-branch=main'], { cwd: dir })
|
|
10
|
+
await writeFile(join(dir, 'a.sh'), '#!/bin/sh\n', 'utf8')
|
|
11
|
+
execFileSync('git', ['add', '-A'], { cwd: dir })
|
|
12
|
+
expect(listShellScriptPaths(dir)).toEqual(['a.sh'])
|
|
13
|
+
})
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Приклад у репо: `rules/text/lint/tests/run-shellcheck.test.mjs` — тест `listShellScriptPaths всередині git-репо`.
|
|
17
|
+
|
|
18
|
+
**Виняток** — top-level smoke-аудит тести, що **за визначенням** перевіряють інваріанти **живого** репо (структура файлів, узгодженість CHANGELOG з `package.json`, кожне правило має fixture). Приклади: `tests/integration-repo-checks.test.mjs`, `tests/check-rule-fixtures.test.mjs`. Такі тести **не дають додаткового coverage у unit-сенсі** — концерни, які вони перевіряють, уже покриті per-rule unit-тестами (`rules/<rule>/js/tests/`). Захищаємо їх `test.skipIf(env.STRYKER_MUTATOR_WORKER)` від запуску всередині Stryker-sandbox:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { env } from 'node:process'
|
|
22
|
+
|
|
23
|
+
test.skipIf(env.STRYKER_MUTATOR_WORKER)('узгоджені з поточним деревом', async () => {
|
|
24
|
+
// …перевірки на REPO_ROOT
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Перевірка** — концерн `sandbox-aware-test` (`rules/test/js/sandbox-aware-test.mjs`): сканер `*.test.{js,mjs}`, який знаходить тести з `import.meta.url`-deep-relative навігацією (4+ `..`-рівнів) і вимагає або `withTmpDir` у їхніх `test`-блоках, або `test.skipIf(env.STRYKER_MUTATOR_WORKER)`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/test/stryker_config/data/stryker_config
|
|
4
|
+
resource: plugins/lang-js/rules/test/stryker_config/data/stryker_config/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/rules/test/stryker_config/data/stryker_config
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ----------------------------------------------------------------- | --------- |
|
|
11
|
+
| [stryker-vue-macros-ignorer.mjs](stryker-vue-macros-ignorer.md) | JS Module |
|
|
12
|
+
| [stryker.config.baseline.mjs](stryker.config.baseline.md) | JS Module |
|
|
13
|
+
| [stryker.config.vue.baseline.mjs](stryker.config.vue.baseline.md) | JS Module |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: stryker-vue-macros-ignorer.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 30a5e9f9
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
tier: local-min
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.97
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Конфігурує механізм Stryker для ігнорування мутацій, які виникають при викликах макросів Vue (`defineProps`, `defineEmits`, `defineModel`, `defineSlots`, `defineExpose`, `defineOptions`) у середовищі `<script setup>`. Це запобігає падінню `@vue/compiler-sfc`, оскільки мутатори стандартного плагіна Stryker обгортають аргументи макросів у тернарний coverage-вираз, що порушує статичний аналіз. Механізм реалізується шляхом реєстрації плагіна у `strykerPlugins: Plugin[]` у `plugin-loader.js` та активації ігнорування за ім'ям `'vue-macros'` у `stryker.config.mjs`.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
Поведінка:
|
|
21
|
+
shouldIgnore: Позначає виклик макроса Vue `<script setup>` як ігнорований для мутації Stryker.
|
|
22
|
+
strykerPlugins: Визначає набір плагінів для Stryker, включаючи ігнорувальник для макросів Vue.
|
|
23
|
+
|
|
24
|
+
## Публічний API
|
|
25
|
+
|
|
26
|
+
shouldIgnore — Встановлює, які мутації викликів Vue `<script setup>`-макросів (`defineProps`, `defineEmits`, `defineModel`, `defineSlots`, `defineExpose`, `defineOptions`) мають бути пропущені аналізом Stryker.
|
|
27
|
+
strykerPlugins — Забезпечує механізм експорту плагінів (`Plugin[]`) для доповнення процесу сканування коду.
|
|
28
|
+
|
|
29
|
+
## Гарантії поведінки
|
|
30
|
+
|
|
31
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: stryker.config.baseline.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 2c7c9c37
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
tier: local-min
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.97
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Файл забезпечує виконання тестових сценаріїв для виявлення дефектів у коді. Він використовує `vitest` для тестування та інтегрує Stryker для мутаційного тестування. Процес збору та збереження результатів мутацій відбувається відповідно до конфігурацій `mutation.json` та `incremental.json`.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
1. Використовує `vitest` як рушій для виконання тестів.
|
|
21
|
+
2. Визначає конфігураційний файл `vitest.config.mjs` для `vitest`.
|
|
22
|
+
3. При зміні коду Stryker запускає лише ті тести, які покривають змінену лінію, що підвищує швидкість порівняно з запуском всього набору тестів для кожного мутанта.
|
|
23
|
+
4. Встановлює тимчасову директорію для звітів як `reports/stryker/.tmp`.
|
|
24
|
+
5. Виводить результати у форматі JSON та у вигляді чистого тексту.
|
|
25
|
+
6. Зберігає деталі мутацій у файлі `reports/stryker/mutation.json`.
|
|
26
|
+
7. Увімкнено інкрементальний режим, що дозволяє відновлювати результати після збоїв, забезпечуючи значне прискорення при повторних запусках.
|
|
27
|
+
8. Процес роботи спирається на конфігураційні файли `mutation.json` та `incremental.json`.
|
|
28
|
+
|
|
29
|
+
## Гарантії поведінки
|
|
30
|
+
|
|
31
|
+
- (специфічних машинно-виведених гарантій немає)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: stryker.config.vue.baseline.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 579865c3
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
tier: local-min
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.97
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Огляд
|
|
17
|
+
Цей файл відповідає за конфігурацію та запуск мутаційного аналізу коду за допомогою Stryker. Він вимірює якість тестування шляхом порівняння покриття вихідного коду тестами. Для налаштування опирається на `mutation.json` та `incremental.json`.
|
|
18
|
+
|
|
19
|
+
Поведінка
|
|
20
|
+
Тестове середовище налаштовується для використання Vitest відповідно до конфігураційного файлу Vitest. Запуск мутаційного аналізу здійснюється за допомогою Stryker, який обробляє кожен тест окремо для підвищення швидкості порівняно із запуском всього тестового набору. Згенеровані результати зберігаються у текстовому та JSON-форматах, причому JSON-звіт поміщається у `reports/stryker/mutation.json`. Для оптимізації подальших аналізів використовується `reports/stryker/incremental.json` для підтримки стану аналізу. Також реалізовано ігнорування мутацій у Vue-макросах `<script setup>` за допомогою спеціального локального плагіна, що запобігає збоям компіляції SFC.
|
|
21
|
+
|
|
22
|
+
## Поведінка
|
|
23
|
+
|
|
24
|
+
1. Визначає середовище виконання тестів як Vitest.
|
|
25
|
+
2. Налаштовує відповідність до файлу конфігурації Vitest.
|
|
26
|
+
3. Запускає Stryker для аналізу коду, обробляючи кожен тест окремо, що підвищує швидкість порівняно з запуском всього тестового набору.
|
|
27
|
+
4. Створює тимчасовий каталог для звітів Stryker.
|
|
28
|
+
5. Збирає результати в текстовий та JSON-формати, зберігаючи JSON-звіт у `reports/stryker/mutation.json`.
|
|
29
|
+
6. Дозволяє відновлення результатів попередніх запусків за допомогою файлу `reports/stryker/incremental.json`, що прискорює повторні запуски.
|
|
30
|
+
7. Використовує спеціальний локальний плагін для ігнорування мутацій у макросах Vue `<script setup>`, щоб уникнути збоїв при компіляції SFC.
|
|
31
|
+
8. Визначає мутації Vue-макросів як ігноровані.
|
|
32
|
+
|
|
33
|
+
## Гарантії поведінки
|
|
34
|
+
|
|
35
|
+
- (специфічних машинно-виведених гарантій немає)
|