@nitra/cursor 5.3.3 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/settings.template.json +2 -2
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +13 -24
- package/CHANGELOG.md +17 -0
- package/bin/n-cursor.js +43 -22
- package/lib/docs/llm.md +23 -12
- package/lib/docs/models.md +29 -18
- package/lib/docs/omlx-trace.md +51 -0
- package/lib/docs/omlx.md +31 -15
- package/lib/omlx.mjs +2 -5
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +17 -11
- package/rules/adr/docs/fix.md +25 -140
- package/rules/bun/docs/fix.md +18 -151
- package/rules/capacitor/docs/fix.md +16 -13
- package/rules/capacitor/js/docs/platforms.md +31 -43
- package/rules/changelog/docs/fix.md +25 -169
- package/rules/ci4/docs/fix.md +11 -14
- package/rules/doc-files/doc-files.mdc +60 -0
- package/rules/doc-files/docs/fix.md +31 -0
- package/rules/doc-files/fix.mjs +19 -0
- package/{skills → rules}/doc-files/js/docgen-extract.mjs +42 -19
- package/{skills → rules}/doc-files/js/docgen-ignore.mjs +2 -1
- package/{skills → rules}/doc-files/js/docgen-scan.mjs +9 -1
- package/{skills → rules}/doc-files/js/docs/docgen-crc.md +1 -1
- package/rules/doc-files/js/docs/docgen-extract-anchors.md +45 -0
- package/rules/doc-files/js/docs/docgen-extract.md +39 -0
- package/rules/doc-files/js/docs/docgen-files-batch.md +35 -0
- package/rules/doc-files/js/docs/docgen-gen.md +46 -0
- package/rules/doc-files/js/docs/docgen-ignore.md +37 -0
- package/rules/doc-files/js/docs/docgen-prompts.md +39 -0
- package/rules/doc-files/js/docs/docgen-scan.md +54 -0
- package/rules/doc-files/js/docs/lint.md +36 -0
- package/rules/doc-files/js/docs/units-js.md +31 -0
- package/rules/doc-files/js/docs/units-rs.md +35 -0
- package/rules/doc-files/js/docs/units.md +30 -0
- package/rules/doc-files/js/lint.mjs +96 -0
- package/{skills → rules}/doc-files/js/units-rs.mjs +37 -17
- package/rules/doc-files/lint/docs/lint.md +37 -0
- package/rules/doc-files/lint/lint.mjs +105 -0
- package/rules/doc-files/meta.json +1 -0
- package/rules/docker/docs/fix.md +21 -161
- package/rules/efes/docs/fix.md +23 -194
- package/rules/feedback/docs/fix.md +10 -8
- package/rules/ga/docs/fix.md +10 -5
- package/rules/graphql/docs/fix.md +23 -119
- package/rules/hasura/docs/fix.md +19 -5
- package/rules/hasura/js/docs/internal_urls.md +34 -307
- package/rules/image-avif/docs/fix.md +16 -127
- package/rules/image-compress/docs/fix.md +20 -141
- package/rules/image-compress/js/docs/package_setup.md +22 -182
- package/rules/js-bun-db/docs/fix.md +23 -139
- package/rules/js-bun-db/js/docs/safety.md +33 -221
- package/rules/js-bun-redis/docs/fix.md +25 -114
- package/rules/js-bun-redis/js/docs/imports.md +18 -166
- package/rules/js-lint/docs/fix.md +30 -108
- package/rules/js-lint/js/docs/lint-findings.md +37 -17
- package/rules/js-lint/js/docs/lint.md +22 -238
- package/rules/js-lint/js/docs/tooling.md +34 -331
- package/rules/js-lint-ci/docs/fix.md +16 -149
- package/rules/js-lint-ci/js/docs/lint.md +16 -136
- package/rules/js-mssql/docs/fix.md +18 -123
- package/rules/js-mssql/js/docs/deps.md +28 -251
- package/rules/js-run/docs/fix.md +23 -138
- package/rules/js-run/js/docs/runtime.md +24 -378
- package/rules/k8s/docs/fix.md +18 -123
- package/rules/nginx-default-tpl/docs/fix.md +22 -118
- package/rules/nginx-default-tpl/js/docs/template.md +38 -360
- package/rules/npm-module/docs/fix.md +27 -89
- package/rules/npm-module/js/docs/header_doc_pointer.md +15 -15
- package/rules/npm-module/js/docs/package_structure.md +36 -258
- package/rules/npm-module/js/docs/rule_meta.md +25 -127
- package/rules/npm-module/js/docs/skill_meta.md +18 -180
- package/rules/php/docs/fix.md +21 -98
- package/rules/php/js/docs/tooling.md +20 -143
- package/rules/python/docs/fix.md +25 -157
- package/rules/python/js/docs/applies.md +20 -98
- package/rules/python/js/docs/tooling.md +27 -144
- package/rules/rego/docs/fix.md +24 -112
- package/rules/rego/js/docs/applies.md +20 -164
- package/rules/rego/js/docs/lint.md +15 -110
- package/rules/release/docs/fix.md +16 -114
- package/rules/rust/docs/fix.md +24 -119
- package/rules/rust/js/docs/applies.md +20 -129
- package/rules/security/docs/fix.md +21 -78
- package/rules/security/js/docs/sample_secret.md +23 -182
- package/rules/security/js/docs/trufflehog.md +19 -128
- package/rules/style-lint/docs/fix.md +16 -150
- package/rules/style-lint/js/docs/lint.md +21 -172
- package/rules/style-lint/js/docs/tooling.md +19 -184
- package/rules/tauri/docs/fix.md +26 -152
- package/rules/tauri/js/docs/cargo_mutants_config.md +21 -159
- package/rules/tauri/js/docs/tooling.md +20 -217
- package/rules/test/docs/fix.md +19 -127
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +15 -127
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +17 -153
- package/rules/test/js/docs/cargo_mutants_config.md +24 -164
- package/rules/test/js/docs/location.md +24 -126
- package/rules/test/js/docs/no-process-chdir.md +20 -151
- package/rules/test/js/docs/no-relative-fs-path.md +24 -261
- package/rules/test/js/docs/stryker_config.md +48 -148
- package/rules/test/js/docs/vitest-config-pool-forks.md +21 -164
- package/rules/text/docs/fix.md +25 -113
- package/rules/text/js/docs/forbidden-prettier.md +21 -132
- package/rules/text/js/docs/formatting.md +60 -251
- package/rules/text/js/docs/lint.md +17 -114
- package/rules/vue/docs/fix.md +25 -118
- package/rules/vue/js/docs/packages.md +25 -323
- package/rules/worktree/docs/fix.md +31 -150
- package/scripts/coverage-classify/docs/index.md +23 -209
- package/scripts/coverage-classify/docs/verdict-schema.md +14 -159
- package/scripts/dispatcher/docs/trace.md +35 -0
- package/scripts/docs/auto-rules.md +37 -361
- package/scripts/docs/lint-cli.md +12 -13
- package/scripts/docs/post-tool-use-fix.md +16 -15
- package/scripts/docs/skills-cli.md +26 -23
- package/scripts/docs/sync-claude-config.md +94 -34
- package/scripts/docs/worktree-cli.md +11 -34
- package/scripts/lib/docs/assert-project-root.md +14 -16
- package/scripts/lib/docs/changed-files.md +24 -139
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +14 -146
- package/scripts/lib/docs/rule-predicates.md +20 -17
- package/scripts/lib/docs/run-rule-cli.md +14 -18
- package/scripts/lib/docs/run-rule.md +13 -20
- package/scripts/lib/docs/run-standard-rule.md +12 -15
- package/scripts/lib/docs/sync-gitignore-worktree.md +15 -18
- package/scripts/lib/rule-predicates.mjs +1 -1
- package/scripts/sync-claude-config.mjs +4 -1
- package/scripts/utils/docs/with-lock.md +19 -12
- package/scripts/utils/with-lock.mjs +4 -2
- package/skills/doc-aggregate/SKILL.md +2 -2
- package/skills/doc-aggregate/js/docgen-ignore.mjs +6 -6
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
- package/skills/doc-aggregate/js/docs/docgen-scan.md +78 -0
- package/skills/doc-files/.changes/260612-0012.md +5 -0
- package/skills/doc-files/.changes/260612-0031.md +5 -0
- package/skills/doc-files/.changes/260612-0036.md +5 -0
- package/skills/doc-files/.changes/260612-0114.md +5 -0
- package/skills/doc-files/SKILL.md +6 -6
- package/skills/fix/js/docs/llm-worker.md +17 -15
- package/skills/fix/js/docs/orchestrator.md +30 -23
- package/skills/fix/js/docs/t0.md +26 -16
- package/skills/start-check/js/docs/check.md +26 -22
- package/skills/taze/js/docs/diff.md +44 -20
- package/skills/doc-files/js/docs/docgen-extract-anchors.md +0 -27
- package/skills/doc-files/js/docs/docgen-extract.md +0 -29
- package/skills/doc-files/js/docs/docgen-files-batch.md +0 -25
- package/skills/doc-files/js/docs/docgen-gen.md +0 -30
- package/skills/doc-files/js/docs/docgen-prompts.md +0 -32
- package/skills/doc-files/js/docs/docgen-scan.md +0 -25
- package/skills/doc-files/js/docs/units-rs.md +0 -35
- /package/{skills → rules}/doc-files/js/docgen-crc.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-extract-anchors.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-files-batch.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-gen.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-prompts.mjs +0 -0
- /package/{skills → rules}/doc-files/js/units-js.mjs +0 -0
- /package/{skills → rules}/doc-files/js/units.mjs +0 -0
|
@@ -1,160 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/test/js/no-process-chdir.mjs
|
|
4
|
+
crc: 0ace6be6
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Модуль `no-process-chdir.mjs` — concern-перевірка (check) з набору правил `test.mdc`, яка **забороняє виклик `process.chdir(...)` у тестових файлах** (`*.test.js`, `*.test.mjs`) проєкту.
|
|
6
|
-
|
|
7
|
-
Мотивація закладена у заголовному JSDoc файлу та повторена в повідомленнях звіту:
|
|
8
|
-
|
|
9
|
-
- `process.chdir` — це **process-wide мутація** робочого каталогу.
|
|
10
|
-
- Vitest за замовчуванням використовує `pool: 'threads'`, у якому workers ділять **один процес**. Це означає, що паралельний `test file` може перехопити `cwd` сусіда посеред FS- або `git`-операції.
|
|
11
|
-
- Зафіксований реальний інцидент: `git init` + `git commit` із tmp-фікстури `withTmpCwd` потрапили у реальний робочий репозиторій і створили rogue-commit з автором `test <test@test>`.
|
|
12
|
-
- Канонічна заміна: `withTmpDir(async dir => …)` зі `scripts/utils/test-helpers.mjs` (без `chdir`), плюс **явні** `cwd: dir` у child-процесах та `await check(dir)` для concern-функцій.
|
|
13
|
-
|
|
14
|
-
Перевірка реалізована як один експортований асинхронний `check(cwd)`, придатний для запуску з runner-а check-репортера. Вона рекурсивно знаходить тестові файли (через `walkDir`), читає їх вміст і шукає викликний паттерн `process.chdir(`. На збігах формується список offenders, для кожного з яких репортер видає `fail`-повідомлення з шляхом і номером рядка; за відсутності збігів видається одне `pass`.
|
|
15
|
-
|
|
16
|
-
Регулярний вираз навмисно вимагає **відкривну дужку**, тому згадки `process.chdir` у коментарях/документації (наприклад, фраза «не використовуй `process.chdir`») **не тригерять** падіння.
|
|
17
|
-
|
|
18
|
-
## Експорти / API
|
|
19
|
-
|
|
20
|
-
| Експорт | Тип | Призначення |
|
|
21
|
-
| ------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
-
| `check` | `async (cwdParam?: string) => Promise<number>` | Іменований експорт. Виконує перевірку всіх тестових файлів у заданому корені й повертає exit-код для CI: `0` — чисто, `1` — знайдено `process.chdir(` у тесті. |
|
|
23
|
-
|
|
24
|
-
Інших публічних експортів файл не містить. Модуль використовує ESM (`import`/`export`).
|
|
25
|
-
|
|
26
|
-
Окрім експорту, у модулі визначені **внутрішні** елементи (не експортуються):
|
|
27
|
-
|
|
28
|
-
- Константа `CHDIR_CALL_RE` — `RegExp`.
|
|
29
|
-
- Функція `isTestFile(absPath)` — допоміжна (file-private).
|
|
30
|
-
|
|
31
|
-
## Функції
|
|
32
|
-
|
|
33
|
-
### `isTestFile(absPath)`
|
|
34
|
-
|
|
35
|
-
Внутрішня (не експортована) функція-предикат.
|
|
36
|
-
|
|
37
|
-
- **Сигнатура:** `function isTestFile(absPath: string): boolean`
|
|
38
|
-
- **Параметри:**
|
|
39
|
-
- `absPath` (`string`) — абсолютний шлях до файлу-кандидата (передається з `walkDir`).
|
|
40
|
-
- **Повертає:** `boolean` — `true`, якщо `basename(absPath)` закінчується на `.test.mjs` або `.test.js`; інакше `false`.
|
|
41
|
-
- **Side effects:** немає (чиста функція; читає лише вхідний рядок через `basename` з `node:path`).
|
|
42
|
-
- **Логіка:**
|
|
43
|
-
1. Бере `basename(absPath)` через `node:path`.
|
|
44
|
-
2. Перевіряє суфікси `.test.mjs` і `.test.js` через `String.prototype.endsWith`.
|
|
45
|
-
3. Повертає диз'юнкцію цих перевірок.
|
|
46
|
-
|
|
47
|
-
### `check(cwdParam = process.cwd())`
|
|
48
|
-
|
|
49
|
-
Головна (і єдина) експортована функція concern-у.
|
|
50
|
-
|
|
51
|
-
- **Сигнатура:** `async function check(cwdParam?: string): Promise<number>`
|
|
52
|
-
- **Параметри:**
|
|
53
|
-
- `cwdParam` (`string`, опціональний) — корінь репозиторію/проєкту, який потрібно перевіряти. Якщо не передано — використовується `process.cwd()`. У документації заголовка та в повідомленнях про інцидент рекомендується **завжди передавати явний `dir`** з тестового середовища (через `await check(dir)`), щоб не залежати від фактичного `process.cwd()` процесу-runner-а.
|
|
54
|
-
- **Повертає:** `Promise<number>` — exit-код, отриманий від `reporter.getExitCode()`:
|
|
55
|
-
- `0` — у жодному тестовому файлі не знайдено виклику `process.chdir(`.
|
|
56
|
-
- `1` — знайдено хоча б один виклик; усі offenders повідомлені як `fail`.
|
|
57
|
-
- **Side effects:**
|
|
58
|
-
- Звертається до файлової системи: рекурсивний обхід через `walkDir` та `readFile` для кожного знайденого тестового файлу.
|
|
59
|
-
- Викликає функції `pass`/`fail` репортера (`createCheckReporter`), які типово друкують повідомлення у `stdout`/`stderr` (поведінка інкапсульована у `check-reporter`).
|
|
60
|
-
- Читає `.n-cursor.json` (через `loadCursorIgnorePaths`) для отримання списку ігнорованих шляхів.
|
|
61
|
-
- Не змінює файлову систему й не змінює `cwd` процесу.
|
|
62
|
-
- **Алгоритм:**
|
|
63
|
-
1. Створює репортер: `const reporter = createCheckReporter()`; деструктурує `pass` і `fail`.
|
|
64
|
-
2. Зберігає `cwd = cwdParam` (локально, без побічних ефектів на процес).
|
|
65
|
-
3. Завантажує список ignore-шляхів: `ignorePaths = await loadCursorIgnorePaths(cwd)`.
|
|
66
|
-
4. Готує локальний акумулятор `testFiles: string[]` і викликає `walkDir(cwd, callback, ignorePaths)`. У callback-у — якщо `isTestFile(absPath)`, додає шлях у масив.
|
|
67
|
-
5. Готує акумулятор `offenders: Array<{file: string, line: number}>`.
|
|
68
|
-
6. Для кожного `absPath` із `testFiles`:
|
|
69
|
-
- Читає вміст: `body = await readFile(absPath, 'utf8')`.
|
|
70
|
-
- Якщо у тілі немає збігу з `CHDIR_CALL_RE` — `continue` (швидкий вихід без побудови `lines`).
|
|
71
|
-
- Інакше розбиває тіло на рядки `body.split('\n')` і для кожного рядка `(i, line)` перевіряє `CHDIR_CALL_RE.test(line)`. На збігу — `offenders.push({ file: relative(cwd, absPath), line: i + 1 })` (номер рядка — 1-базований).
|
|
72
|
-
7. Якщо `offenders.length === 0` — викликає `pass(`Жоден з ${testFiles.length} тестових файлів не викликає process.chdir() (test.mdc)`)` і повертає `reporter.getExitCode()`.
|
|
73
|
-
8. Інакше — для кожного offender викликає `fail` з повідомленням формату `${file}:${line}: process.chdir() у тесті заборонений — використовуй withTmpDir(async dir => …) + явні join(dir, …) + cwd: dir (test.mdc)` і у фіналі повертає `reporter.getExitCode()`.
|
|
74
|
-
|
|
75
|
-
## Константи і регулярні вирази
|
|
8
|
+
# no-process-chdir.mjs
|
|
76
9
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```js
|
|
80
|
-
const CHDIR_CALL_RE = /process\.chdir\s*\(/u
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
- **Призначення:** виявити **викликний** паттерн `process.chdir(`.
|
|
84
|
-
- **Семантика:**
|
|
85
|
-
- `process\.chdir` — буквальне ім'я виклику (крапка екранована).
|
|
86
|
-
- `\s*` — допускає будь-яку кількість пробільних символів між ідентифікатором і дужкою.
|
|
87
|
-
- `\(` — обов'язкова відкривна дужка; саме вона відрізняє виклик від згадки в коментарі/документації.
|
|
88
|
-
- Прапор `u` — Unicode-режим.
|
|
89
|
-
- **Наслідки дизайну:**
|
|
90
|
-
- Згадки `process.chdir` у JSDoc/коментарях без `(` **не** дають збігу (це навмисно і задокументовано).
|
|
91
|
-
- Регулярка лінійна, без backtracking-ризиків; виконується спочатку проти всього `body` (швидкий short-circuit), потім — порядково для локалізації номера рядка.
|
|
92
|
-
|
|
93
|
-
## Залежності
|
|
94
|
-
|
|
95
|
-
### Зовнішні (стандартна бібліотека Node)
|
|
96
|
-
|
|
97
|
-
- `node:fs/promises` → іменований імпорт `readFile` — асинхронне читання тестових файлів у режимі `'utf8'`.
|
|
98
|
-
- `node:path` → іменовані імпорти:
|
|
99
|
-
- `basename` — у `isTestFile` для перевірки суфікса.
|
|
100
|
-
- `relative` — для перетворення абсолютного шляху offender-а в шлях, відносний до `cwd`, перед публікацією у `fail`-повідомленні.
|
|
101
|
-
|
|
102
|
-
### Внутрішні (репозиторій)
|
|
103
|
-
|
|
104
|
-
Усі — відносні шляхи від `npm/rules/test/js/no-process-chdir.mjs`:
|
|
105
|
-
|
|
106
|
-
- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика репортера; повертає об'єкт з методами `pass`, `fail` і `getExitCode()`. Інкапсулює формат виведення й підрахунок exit-коду.
|
|
107
|
-
- `../../../scripts/lib/load-cursor-config.mjs` → `loadCursorIgnorePaths` — повертає список ігнорованих шляхів із `.n-cursor.json` (поле `ignore`), які треба передати у `walkDir` як другий рівень фільтрації (на додаток до вбудованих скіпів самого `walkDir`).
|
|
108
|
-
- `../../../scripts/utils/walkDir.mjs` → `walkDir` — рекурсивний обхід дерева; за заголовним коментарем модуля вбудовано пропускає `node_modules`, `.git`, `dist`, `build`, `.venv`, `venv`. Викликає callback з абсолютним шляхом для кожного відвіданого файлу.
|
|
109
|
-
|
|
110
|
-
## Потік виконання / Використання
|
|
111
|
-
|
|
112
|
-
### Запуск як частина check-набору
|
|
113
|
-
|
|
114
|
-
Модуль не виконується автоматично при імпорті — це **бібліотечний** файл concern-а: він експортує `check`, який має бути викликаний runner-ом (наприклад, агрегатором правил `test.mdc`). Типовий виклик:
|
|
115
|
-
|
|
116
|
-
```js
|
|
117
|
-
import { check } from './npm/rules/test/js/no-process-chdir.mjs'
|
|
118
|
-
|
|
119
|
-
const code = await check(process.cwd())
|
|
120
|
-
process.exit(code)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Або з явним коренем (як рекомендовано у заголовному JSDoc — для concern-функцій передавати `dir`, а не покладатися на process-wide `cwd`):
|
|
124
|
-
|
|
125
|
-
```js
|
|
126
|
-
await check(dir) // dir отриманий з withTmpDir або з runner-а
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Послідовність кроків під час одного виклику
|
|
130
|
-
|
|
131
|
-
1. `createCheckReporter()` створює пристрій для накопичення pass/fail повідомлень.
|
|
132
|
-
2. `loadCursorIgnorePaths(cwd)` читає `.n-cursor.json` (поле `ignore`) у корені проєкту.
|
|
133
|
-
3. `walkDir(cwd, callback, ignorePaths)` обходить дерево, пропускаючи стандартні теки (`node_modules`, `.git`, `dist`, `build`, `.venv`, `venv`) і шляхи з ignore-списку. Колбек збирає шляхи, що задовольняють `isTestFile`.
|
|
134
|
-
4. Для кожного зібраного тестового файлу:
|
|
135
|
-
- Швидкий тест регуляркою проти повного тексту — для більшості файлів ціна перевірки = `O(N)` без подальшої роботи.
|
|
136
|
-
- Якщо є збіг, файл розбивається на рядки, і для кожного рядка з виявленим паттерном до `offenders` додається `{file, line}` (шлях нормалізовано через `relative(cwd, absPath)`, номер рядка — 1-базований).
|
|
137
|
-
5. Формування результату:
|
|
138
|
-
- Порожній `offenders` → одна `pass`-нотатка з кількістю просканованих файлів.
|
|
139
|
-
- Непорожній — окремий `fail` на кожен `{file, line}` з підказкою про канонічну заміну.
|
|
140
|
-
6. Повертається `reporter.getExitCode()` — `0` або `1`.
|
|
10
|
+
## Огляд
|
|
141
11
|
|
|
142
|
-
|
|
12
|
+
Огляд
|
|
13
|
+
Файл шукає викликний паттерн process.chdir у тестових файлах. Він перевіряє, чи викликає будь-який тестовий файл process.chdir, і генерує повідомлення про порушення (test.mdc).
|
|
143
14
|
|
|
144
|
-
|
|
145
|
-
- `loadCursorIgnorePaths`/`walkDir`/`createCheckReporter` помилки також пробрасуються у викликача.
|
|
146
|
-
- Модуль не модифікує глобальний стан і не змінює `cwd` процесу.
|
|
15
|
+
## Поведінка
|
|
147
16
|
|
|
148
|
-
|
|
17
|
+
1. Шукає викликний паттерн з відкривною дужкою process.chdir у тестових файлах.
|
|
18
|
+
2. Перевіряє, чи викликає будь-який тестовий файл process.chdir.
|
|
19
|
+
3. Якщо знайдено виклик, генерує повідомлення про порушення.
|
|
20
|
+
4. Повертає код виходу відповідно до результату перевірки. (test.mdc)
|
|
149
21
|
|
|
150
|
-
|
|
151
|
-
- **Fail** (по одному рядку на кожен збіг): `path/to/file.test.mjs:123: process.chdir() у тесті заборонений — використовуй withTmpDir(async dir => …) + явні join(dir, …) + cwd: dir (test.mdc)`.
|
|
22
|
+
## Публічний API
|
|
152
23
|
|
|
153
|
-
|
|
24
|
+
check — перевіряє, чи жоден файл з розширенням `*.test.{mjs,js}` не виконує `process.chdir(`. (test.mdc)
|
|
154
25
|
|
|
155
|
-
|
|
26
|
+
## Гарантії поведінки
|
|
156
27
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
- У викликах child-процесів передавати `cwd: dir` як опцію.
|
|
160
|
-
- У concern-функціях — приймати `cwd`/`dir` параметром і не покладатися на `process.cwd()`.
|
|
28
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
29
|
+
- Не звертається до мережі.
|
|
@@ -1,271 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/test/js/no-relative-fs-path.mjs
|
|
4
|
+
crc: 3b8f52db
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# no-relative-fs-path.mjs
|
|
2
9
|
|
|
3
10
|
## Огляд
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
helper-функції, що вимагають абсолютних шляхів) усередині JS-тестів
|
|
8
|
-
(`*.test.mjs` / `*.test.js`).
|
|
9
|
-
|
|
10
|
-
Мотивація задокументована у самому файлі та у правилі `test.mdc`, секція
|
|
11
|
-
«Заборона `process.chdir` у тестах»: після видалення хелпера `withTmpCwd` усі
|
|
12
|
-
тести отримують `dir` параметром і повинні будувати **абсолютні** шляхи через
|
|
13
|
-
`join(dir, …)`. Якщо хтось забуде префікс і напише, наприклад,
|
|
14
|
-
`writeFile('foo.json', …)` або `copyFile(src, 'foo.json')`, relative-path
|
|
15
|
-
зарезолвиться у `process.cwd()` (= `npm/`), що призведе до запису тестової
|
|
16
|
-
фікстури у production tree. Реальний інцидент v1.28.0
|
|
17
|
-
(`tests/check-rule-fixtures.test.mjs` із викликами
|
|
18
|
-
`copyFile(src, 'values-dev.ini')` та
|
|
19
|
-
`copyFile(src, 'default.conf.template')`) створив файли `npm/values-dev.ini` і
|
|
20
|
-
`npm/default.conf.template` поза `dir`.
|
|
21
|
-
|
|
22
|
-
Сканер парсить кожен тестовий файл через `oxc-parser` (через утиліту
|
|
23
|
-
`parseProgramOrNull`), обходить AST і шукає `CallExpression`, де callee
|
|
24
|
-
збігається з відомою FS-функцією, а path-аргумент є **string literal** (або
|
|
25
|
-
template literal без виразів), що НЕ починається з:
|
|
26
|
-
|
|
27
|
-
- `/`, `\\` — POSIX/Windows absolute;
|
|
28
|
-
- `file:`, `http:`, `https:`, `data:` — URL-схема (для `new URL(...)`);
|
|
29
|
-
- Windows drive letter `C:\…` або `C:/…`;
|
|
30
|
-
- та НЕ є template literal зі вставленим виразом `${…}` (такі вважаються
|
|
31
|
-
обчисленими через `join`/`resolve` і пропускаються).
|
|
32
|
-
|
|
33
|
-
Виклики, у яких path-аргумент НЕ literal (наприклад, `join(...)`,
|
|
34
|
-
`BinaryExpression`, `Identifier`, `MemberExpression`), пропускаються —
|
|
35
|
-
припускається, що це абсолютний шлях.
|
|
36
|
-
|
|
37
|
-
Перевіряються лише файли, що відповідають `**/*.test.{js,mjs}`. Обхід дерева
|
|
38
|
-
використовує загальний `walkDir` з його скіп-правилами та користувацькими
|
|
39
|
-
`ignore`-шляхами з `.n-cursor.json`.
|
|
40
|
-
|
|
41
|
-
## Експорти / API
|
|
42
|
-
|
|
43
|
-
| Експорт | Тип | Призначення |
|
|
44
|
-
| ------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
45
|
-
| `check(cwdParam?)` | `async function` | Точка входу перевірки. Сканує `*.test.{mjs,js}` під `cwd`, повертає exit code `0` (чисто) або `1` (є порушення). |
|
|
46
|
-
|
|
47
|
-
Усі решта функцій (`extractRelativeLiteralPath`, `isRelativeString`,
|
|
48
|
-
`extractFsFunctionName`, `isTestFile`, `findOffendersInBody`,
|
|
49
|
-
`computeLineOffsets`, `offsetToLineFromCache`) та константи
|
|
50
|
-
(`FS_PATH_ARG_POSITIONS`, `ABSOLUTE_PREFIXES`) — module-private, не
|
|
51
|
-
експортуються.
|
|
52
|
-
|
|
53
|
-
## Функції
|
|
54
|
-
|
|
55
|
-
### `extractRelativeLiteralPath(arg)`
|
|
56
|
-
|
|
57
|
-
- **Сигнатура:** `(arg: object) => string | null`
|
|
58
|
-
- **Параметри:**
|
|
59
|
-
- `arg` — AST node аргументу виклику (Literal, TemplateLiteral тощо) або
|
|
60
|
-
`undefined`.
|
|
61
|
-
- **Повертає:** значення relative-path (рядок) — якщо аргумент є
|
|
62
|
-
string-літералом або template literal без виразів і його значення є
|
|
63
|
-
відносним; `null` — якщо аргумент відсутній, обчислюваний або абсолютний.
|
|
64
|
-
- **Логіка:**
|
|
65
|
-
- `arg.type === 'Literal' && typeof arg.value === 'string'` →
|
|
66
|
-
`isRelativeString(arg.value) ? arg.value : null`.
|
|
67
|
-
- `arg.type === 'TemplateLiteral' && expressions.length === 0` →
|
|
68
|
-
конкатенує `quasis[i].value.cooked` і так само перевіряє через
|
|
69
|
-
`isRelativeString`.
|
|
70
|
-
- Будь-який інший вузол → `null` (не аналізується).
|
|
71
|
-
- **Side effects:** немає.
|
|
72
|
-
|
|
73
|
-
### `isRelativeString(s)`
|
|
74
|
-
|
|
75
|
-
- **Сигнатура:** `(s: string) => boolean`
|
|
76
|
-
- **Параметри:** `s` — рядок-шлях.
|
|
77
|
-
- **Повертає:** `true` — якщо рядок виглядає як relative path; `false` — якщо
|
|
78
|
-
абсолютний, URL, Windows-drive-letter або порожній.
|
|
79
|
-
- **Логіка:**
|
|
80
|
-
- Порожній рядок → `false` (не path).
|
|
81
|
-
- Якщо починається з будь-якого з `ABSOLUTE_PREFIXES`
|
|
82
|
-
(`/`, `\\`, `file:`, `http:`, `https:`, `data:`) → `false`.
|
|
83
|
-
- Якщо відповідає `^[A-Za-z]:[\\/]/u` (наприклад, `C:\foo`, `C:/foo`) →
|
|
84
|
-
`false`.
|
|
85
|
-
- Інакше → `true`.
|
|
86
|
-
- **Side effects:** немає.
|
|
87
|
-
|
|
88
|
-
### `extractFsFunctionName(callee)`
|
|
89
|
-
|
|
90
|
-
- **Сигнатура:** `(callee: object) => string | null`
|
|
91
|
-
- **Параметри:** `callee` — AST callee node (Identifier або MemberExpression).
|
|
92
|
-
- **Повертає:** ім'я FS-функції з `FS_PATH_ARG_POSITIONS`, якщо callee
|
|
93
|
-
розпізнано; інакше `null`.
|
|
94
|
-
- **Логіка:**
|
|
95
|
-
- `Identifier` → перевіряє `callee.name` у `FS_PATH_ARG_POSITIONS`.
|
|
96
|
-
- `MemberExpression`, не `computed`, `property.type === 'Identifier'` →
|
|
97
|
-
бере `callee.property.name` (це покриває `fs.writeFile`, `fsp.writeFile`,
|
|
98
|
-
`fs.promises.writeFile` тощо) і перевіряє у мапі.
|
|
99
|
-
- Інакше → `null`.
|
|
100
|
-
- **Side effects:** немає.
|
|
101
|
-
|
|
102
|
-
### `isTestFile(absPath)`
|
|
103
|
-
|
|
104
|
-
- **Сигнатура:** `(absPath: string) => boolean`
|
|
105
|
-
- **Параметри:** `absPath` — абсолютний шлях файлу.
|
|
106
|
-
- **Повертає:** `true`, якщо `basename(absPath)` закінчується на
|
|
107
|
-
`.test.mjs` або `.test.js`; інакше `false`.
|
|
108
|
-
- **Side effects:** немає.
|
|
109
|
-
|
|
110
|
-
### `findOffendersInBody(body)`
|
|
111
|
-
|
|
112
|
-
- **Сигнатура:**
|
|
113
|
-
`(body: string) => Array<{ line: number, fn: string, path: string, argPos: number }>`
|
|
114
|
-
- **Параметри:** `body` — вміст тестового файлу (UTF-8).
|
|
115
|
-
- **Повертає:** масив порушень: `{ line, fn, path, argPos }`, де:
|
|
116
|
-
- `line` — 1-індексований рядок початку аргументу (або виклику, якщо у
|
|
117
|
-
аргументу немає `.start`);
|
|
118
|
-
- `fn` — ім'я FS-функції;
|
|
119
|
-
- `path` — фактичне значення relative-літералу;
|
|
120
|
-
- `argPos` — 0-індексована позиція проблемного аргументу.
|
|
121
|
-
- **Логіка:**
|
|
122
|
-
1. `parseProgramOrNull(body, 'test.mjs')` — парс через oxc-parser із
|
|
123
|
-
використанням віртуального імені; якщо парс не вдався, повертає `[]`.
|
|
124
|
-
2. Кешує newline-offsets через `computeLineOffsets`.
|
|
125
|
-
3. `walkAstWithAncestors(program, [], cb)` — обходить усі вузли.
|
|
126
|
-
Для кожного `CallExpression`:
|
|
127
|
-
- визначає `fnName = extractFsFunctionName(node.callee)`; якщо `null` —
|
|
128
|
-
пропускає;
|
|
129
|
-
- для кожної позиції з `FS_PATH_ARG_POSITIONS.get(fnName)` бере
|
|
130
|
-
`node.arguments[pos]`, перевіряє через `extractRelativeLiteralPath`;
|
|
131
|
-
- якщо `relPath !== null` — обчислює `line` через
|
|
132
|
-
`offsetToLineFromCache(lineOffsets, arg?.start ?? node.start ?? 0)` і
|
|
133
|
-
додає об'єкт у `offenders`.
|
|
134
|
-
- **Side effects:** немає. Парсинг через `parseProgramOrNull` сам по собі
|
|
135
|
-
чистий.
|
|
136
|
-
|
|
137
|
-
### `computeLineOffsets(body)`
|
|
138
|
-
|
|
139
|
-
- **Сигнатура:** `(body: string) => number[]`
|
|
140
|
-
- **Параметри:** `body` — джерельний рядок.
|
|
141
|
-
- **Повертає:** масив 0-індексованих offset-ів початків рядків
|
|
142
|
-
(елемент `0` — позиція `0`, далі позиція кожного символу після `\n`).
|
|
143
|
-
- **Логіка:** лінійний прохід по символах; на кожному `\n` додає `pos + 1`.
|
|
144
|
-
- **Side effects:** немає.
|
|
145
|
-
|
|
146
|
-
### `offsetToLineFromCache(offsets, offset)`
|
|
147
|
-
|
|
148
|
-
- **Сигнатура:** `(offsets: number[], offset: number) => number`
|
|
149
|
-
- **Параметри:**
|
|
150
|
-
- `offsets` — кеш із `computeLineOffsets`;
|
|
151
|
-
- `offset` — 0-індекс символу у source.
|
|
152
|
-
- **Повертає:** 1-індексований номер рядка, що містить цей offset.
|
|
153
|
-
- **Логіка:** бінарний пошук правого діапазону (`lo`/`hi` із кроком
|
|
154
|
-
`mid = floor((lo + hi + 1) / 2)`); кінцевий `lo + 1` — номер рядка.
|
|
155
|
-
- **Side effects:** немає.
|
|
156
|
-
|
|
157
|
-
### `check(cwdParam = process.cwd())`
|
|
158
|
-
|
|
159
|
-
- **Сигнатура:** `async (cwdParam?: string) => Promise<number>`
|
|
160
|
-
- **Параметри:** `cwdParam` — корінь репозиторію (за замовчуванням
|
|
161
|
-
`process.cwd()`).
|
|
162
|
-
- **Повертає:** `0` — порушень немає; `1` — є хоча б одне порушення
|
|
163
|
-
(через `reporter.getExitCode()`).
|
|
164
|
-
- **Логіка:**
|
|
165
|
-
1. Створює репортер: `createCheckReporter()`, дістає `pass` і `fail`.
|
|
166
|
-
2. Завантажує користувацькі ignore-шляхи через `loadCursorIgnorePaths(cwd)`
|
|
167
|
-
(читає `.n-cursor.json#ignore`).
|
|
168
|
-
3. Через `walkDir(cwd, cb, ignorePaths)` збирає у масив `testFiles` усі
|
|
169
|
-
`absPath`, для яких `isTestFile(absPath) === true`.
|
|
170
|
-
4. Для кожного тестового файлу: читає через
|
|
171
|
-
`readFile(absPath, 'utf8')`, запускає `findOffendersInBody(body)`, до
|
|
172
|
-
кожного знахідки додає `file: relative(cwd, absPath)`.
|
|
173
|
-
5. Якщо `offenders.length === 0` — викликає
|
|
174
|
-
`pass(\`Жоден з ${testFiles.length} тестових файлів не передає relative-path у FS-функції (test.mdc)\`)`і повертає`reporter.getExitCode()`.
|
|
175
|
-
6. Інакше — для кожного порушення викликає `fail(...)` із повідомленням
|
|
176
|
-
виду `${file}:${line}: ${fn}() — ${which} '${path}' relative; використовуй join(dir, …) (test.mdc, no-relative-fs-path)`,
|
|
177
|
-
де `which` = `'1-й аргумент'` для `argPos === 0` і
|
|
178
|
-
`'${argPos + 1}-й аргумент'` для решти.
|
|
179
|
-
- **Side effects:**
|
|
180
|
-
- Читає файли з диска (через `readFile` та `walkDir`).
|
|
181
|
-
- Пише у stdout/stderr через репортер (`pass`/`fail`).
|
|
182
|
-
- Не змінює файли.
|
|
183
|
-
|
|
184
|
-
## Константи
|
|
185
|
-
|
|
186
|
-
### `FS_PATH_ARG_POSITIONS`
|
|
187
|
-
|
|
188
|
-
`Map<string, number[]>` — імена FS-функцій → масив 0-індексованих позицій
|
|
189
|
-
path-аргументів. Включає:
|
|
190
|
-
|
|
191
|
-
- з одним path-аргументом (`[0]`): `writeFile`, `writeFileSync`, `readFile`,
|
|
192
|
-
`readFileSync`, `appendFile`, `appendFileSync`, `mkdir`, `mkdirSync`,
|
|
193
|
-
`rmdir`, `rmdirSync`, `rm`, `rmSync`, `unlink`, `unlinkSync`, `access`,
|
|
194
|
-
`accessSync`, `stat`, `statSync`, `lstat`, `lstatSync`, `chmod`,
|
|
195
|
-
`chmodSync`, `chown`, `chownSync`, `truncate`, `truncateSync`,
|
|
196
|
-
`existsSync`, `readdir`, `readdirSync`;
|
|
197
|
-
- з двома path-аргументами (`[0, 1]`): `copyFile`, `copyFileSync`, `rename`,
|
|
198
|
-
`renameSync`, `symlink`, `symlinkSync`, `link`, `linkSync`, `cp`, `cpSync`;
|
|
199
|
-
- тестові-хелпери (зайвий захист, тільки 1-й): `writeJson`, `ensureDir`.
|
|
200
|
-
|
|
201
|
-
### `ABSOLUTE_PREFIXES`
|
|
202
|
-
|
|
203
|
-
`string[]` зі значенням `['/', '\\', 'file:', 'http:', 'https:', 'data:']` —
|
|
204
|
-
префікси, які вважаються «явно абсолютним або URL-шляхом» і виключають
|
|
205
|
-
рядок зі списку relative-path.
|
|
206
|
-
|
|
207
|
-
## Залежності
|
|
208
|
-
|
|
209
|
-
Зовнішні / стандартні модулі:
|
|
210
|
-
|
|
211
|
-
- `node:fs/promises` → `readFile` — читання тестових файлів.
|
|
212
|
-
- `node:path` → `basename`, `relative` — визначення імені файлу та шляху
|
|
213
|
-
відносно `cwd` для повідомлень.
|
|
214
|
-
|
|
215
|
-
Внутрішні модулі (відносні шляхи у репозиторії):
|
|
216
|
-
|
|
217
|
-
- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — стандартний
|
|
218
|
-
репортер перевірок (методи `pass`, `fail`, `getExitCode`).
|
|
219
|
-
- `../../../scripts/lib/load-cursor-config.mjs` → `loadCursorIgnorePaths` —
|
|
220
|
-
читає `.n-cursor.json#ignore` для виключення шляхів.
|
|
221
|
-
- `../../../scripts/utils/ast-scan-utils.mjs` → `parseProgramOrNull`,
|
|
222
|
-
`walkAstWithAncestors` — обгортка над oxc-parser і AST-обхід з
|
|
223
|
-
трекінгом ancestors (тут ancestors не використовуються — `[]`).
|
|
224
|
-
- `../../../scripts/utils/walkDir.mjs` → `walkDir` — рекурсивний обхід
|
|
225
|
-
директорії із загальними skip-правилами та підтримкою `ignorePaths`.
|
|
226
|
-
|
|
227
|
-
## Потік виконання / Використання
|
|
12
|
+
Огляд
|
|
13
|
+
Модуль виконує перевірку тестових файлів на наявність визначених порушень. Перевіряється наявність певних порушень та використання абсолютних шляхів у викликах функцій.
|
|
228
14
|
|
|
229
|
-
|
|
230
|
-
викликається через загальний механізм запуску правил (зазвичай
|
|
231
|
-
`bun n-cursor rules` або еквівалентну команду). Загальний потік виклику
|
|
232
|
-
`check(cwd)`:
|
|
15
|
+
## Поведінка
|
|
233
16
|
|
|
234
|
-
1.
|
|
235
|
-
2.
|
|
236
|
-
3.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- `walkAstWithAncestors` обходить AST і кожен `CallExpression`
|
|
243
|
-
перевіряється проти `FS_PATH_ARG_POSITIONS`;
|
|
244
|
-
- кожен виявлений relative-path-літерал перетворюється на запис
|
|
245
|
-
`offender` з `file`/`line`/`fn`/`path`/`argPos`.
|
|
246
|
-
5. Якщо порушень немає — викликається `pass(...)` і
|
|
247
|
-
повертається `reporter.getExitCode()` (зазвичай `0`).
|
|
248
|
-
6. Якщо є — кожне порушення емітиться через `fail(...)` із заздалегідь
|
|
249
|
-
сформатованим повідомленням; підсумковий exit code — `1`.
|
|
17
|
+
1. Завантажити ігноровані шляхи з корінням репозиторію
|
|
18
|
+
2. Просканувати директорію репозиторію, відфільтровуючи тестові файли
|
|
19
|
+
3. Для кожного тестового файлу прочитати його вміст
|
|
20
|
+
4. Для кожного прочитаного вмісту знайти порушення у коді
|
|
21
|
+
5. Для кожного знайденого порушення визначити рядок, номер рядка та позицію виклику
|
|
22
|
+
6. Для кожного порушення перевірити, чи є перший аргумент у виклику FS-функції шляхом, що не є абсолютним
|
|
23
|
+
7. Якщо порушення знайдено, повернути код помилки
|
|
24
|
+
8. Якщо порушення не знайдено, повернути код успіху
|
|
250
25
|
|
|
251
|
-
|
|
252
|
-
(адже повідомлення містять згадку `(test.mdc, no-relative-fs-path)`),
|
|
253
|
-
де `cwd` — корінь монорепо. Файл є частиною підкаталогу
|
|
254
|
-
`npm/rules/test/js/` (правила, що відповідають `test.mdc`).
|
|
26
|
+
## Публічний API
|
|
255
27
|
|
|
256
|
-
|
|
28
|
+
check — Перевіряє, що жоден `*.test.{mjs,js}` файл не передає relative-path як 1-й аргумент у FS-функцію з `node:fs` / `node:fs/promises` (або для `copyFile`/`rename`/`symlink`/`link`/`cp` — 1-й і 2-й аргумент). (test.mdc)
|
|
257
29
|
|
|
258
|
-
|
|
259
|
-
без помилки.
|
|
260
|
-
- Шлях обчислюється через `join`/`resolve` → пропускається (припускається
|
|
261
|
-
абсолютний).
|
|
262
|
-
- Template literal зі вставленим виразом (`expressions.length > 0`) →
|
|
263
|
-
пропускається.
|
|
264
|
-
- `existsSync` та інші sync-варіанти аналізуються нарівні з async.
|
|
265
|
-
- Виклики через `fs.promises.X` — обробляються коректно, бо
|
|
266
|
-
`extractFsFunctionName` бере `callee.property.name` і не залежить від
|
|
267
|
-
глибини доступу.
|
|
30
|
+
## Гарантії поведінки
|
|
268
31
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
32
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
33
|
+
- Кешує результати в межах одного прогону.
|
|
34
|
+
- Не звертається до мережі.
|