@nitra/cursor 5.3.4 → 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.
Files changed (151) hide show
  1. package/.claude-template/settings.template.json +2 -2
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +13 -24
  3. package/CHANGELOG.md +11 -0
  4. package/bin/n-cursor.js +43 -22
  5. package/lib/docs/models.md +29 -18
  6. package/lib/docs/omlx-trace.md +51 -0
  7. package/lib/docs/omlx.md +31 -15
  8. package/lib/omlx.mjs +2 -5
  9. package/package.json +1 -1
  10. package/rules/abie/docs/fix.md +17 -11
  11. package/rules/adr/docs/fix.md +25 -140
  12. package/rules/bun/docs/fix.md +18 -151
  13. package/rules/capacitor/docs/fix.md +16 -13
  14. package/rules/capacitor/js/docs/platforms.md +31 -43
  15. package/rules/changelog/docs/fix.md +25 -169
  16. package/rules/ci4/docs/fix.md +11 -14
  17. package/rules/doc-files/doc-files.mdc +60 -0
  18. package/rules/doc-files/docs/fix.md +31 -0
  19. package/rules/doc-files/fix.mjs +19 -0
  20. package/{skills → rules}/doc-files/js/docgen-extract.mjs +42 -19
  21. package/{skills → rules}/doc-files/js/docgen-ignore.mjs +2 -1
  22. package/{skills → rules}/doc-files/js/docgen-scan.mjs +9 -1
  23. package/{skills → rules}/doc-files/js/docs/docgen-crc.md +1 -1
  24. package/{skills → rules}/doc-files/js/docs/docgen-extract-anchors.md +1 -1
  25. package/{skills → rules}/doc-files/js/docs/docgen-extract.md +2 -2
  26. package/{skills → rules}/doc-files/js/docs/docgen-files-batch.md +1 -1
  27. package/{skills → rules}/doc-files/js/docs/docgen-gen.md +1 -1
  28. package/{skills → rules}/doc-files/js/docs/docgen-ignore.md +4 -4
  29. package/rules/doc-files/js/docs/docgen-prompts.md +39 -0
  30. package/rules/doc-files/js/docs/docgen-scan.md +54 -0
  31. package/rules/doc-files/js/docs/lint.md +36 -0
  32. package/rules/doc-files/js/docs/units-js.md +31 -0
  33. package/rules/doc-files/js/docs/units-rs.md +35 -0
  34. package/rules/doc-files/js/docs/units.md +30 -0
  35. package/rules/doc-files/js/lint.mjs +96 -0
  36. package/{skills → rules}/doc-files/js/units-rs.mjs +37 -17
  37. package/rules/doc-files/lint/docs/lint.md +37 -0
  38. package/rules/doc-files/lint/lint.mjs +105 -0
  39. package/rules/doc-files/meta.json +1 -0
  40. package/rules/docker/docs/fix.md +21 -161
  41. package/rules/efes/docs/fix.md +23 -194
  42. package/rules/feedback/docs/fix.md +10 -8
  43. package/rules/ga/docs/fix.md +10 -5
  44. package/rules/graphql/docs/fix.md +23 -119
  45. package/rules/hasura/docs/fix.md +19 -5
  46. package/rules/hasura/js/docs/internal_urls.md +34 -307
  47. package/rules/image-avif/docs/fix.md +16 -127
  48. package/rules/image-compress/docs/fix.md +20 -141
  49. package/rules/image-compress/js/docs/package_setup.md +22 -182
  50. package/rules/js-bun-db/docs/fix.md +23 -139
  51. package/rules/js-bun-db/js/docs/safety.md +33 -221
  52. package/rules/js-bun-redis/docs/fix.md +25 -114
  53. package/rules/js-bun-redis/js/docs/imports.md +18 -166
  54. package/rules/js-lint/docs/fix.md +30 -108
  55. package/rules/js-lint/js/docs/lint-findings.md +37 -17
  56. package/rules/js-lint/js/docs/lint.md +22 -238
  57. package/rules/js-lint/js/docs/tooling.md +34 -331
  58. package/rules/js-lint-ci/docs/fix.md +16 -149
  59. package/rules/js-lint-ci/js/docs/lint.md +16 -136
  60. package/rules/js-mssql/docs/fix.md +18 -123
  61. package/rules/js-mssql/js/docs/deps.md +28 -251
  62. package/rules/js-run/docs/fix.md +23 -138
  63. package/rules/js-run/js/docs/runtime.md +24 -378
  64. package/rules/k8s/docs/fix.md +18 -123
  65. package/rules/nginx-default-tpl/docs/fix.md +22 -118
  66. package/rules/nginx-default-tpl/js/docs/template.md +38 -360
  67. package/rules/npm-module/docs/fix.md +27 -89
  68. package/rules/npm-module/js/docs/header_doc_pointer.md +15 -15
  69. package/rules/npm-module/js/docs/package_structure.md +36 -258
  70. package/rules/npm-module/js/docs/rule_meta.md +25 -127
  71. package/rules/npm-module/js/docs/skill_meta.md +18 -180
  72. package/rules/php/docs/fix.md +21 -98
  73. package/rules/php/js/docs/tooling.md +20 -143
  74. package/rules/python/docs/fix.md +25 -157
  75. package/rules/python/js/docs/applies.md +20 -98
  76. package/rules/python/js/docs/tooling.md +27 -144
  77. package/rules/rego/docs/fix.md +24 -112
  78. package/rules/rego/js/docs/applies.md +20 -164
  79. package/rules/rego/js/docs/lint.md +15 -110
  80. package/rules/release/docs/fix.md +16 -114
  81. package/rules/rust/docs/fix.md +24 -119
  82. package/rules/rust/js/docs/applies.md +20 -129
  83. package/rules/security/docs/fix.md +21 -78
  84. package/rules/security/js/docs/sample_secret.md +23 -182
  85. package/rules/security/js/docs/trufflehog.md +19 -128
  86. package/rules/style-lint/docs/fix.md +16 -150
  87. package/rules/style-lint/js/docs/lint.md +21 -172
  88. package/rules/style-lint/js/docs/tooling.md +19 -184
  89. package/rules/tauri/docs/fix.md +26 -152
  90. package/rules/tauri/js/docs/cargo_mutants_config.md +21 -159
  91. package/rules/tauri/js/docs/tooling.md +20 -217
  92. package/rules/test/docs/fix.md +19 -127
  93. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +15 -127
  94. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +17 -153
  95. package/rules/test/js/docs/cargo_mutants_config.md +24 -164
  96. package/rules/test/js/docs/location.md +24 -126
  97. package/rules/test/js/docs/no-process-chdir.md +20 -151
  98. package/rules/test/js/docs/no-relative-fs-path.md +24 -261
  99. package/rules/test/js/docs/stryker_config.md +48 -148
  100. package/rules/test/js/docs/vitest-config-pool-forks.md +21 -164
  101. package/rules/text/docs/fix.md +25 -113
  102. package/rules/text/js/docs/forbidden-prettier.md +21 -132
  103. package/rules/text/js/docs/formatting.md +60 -251
  104. package/rules/text/js/docs/lint.md +17 -114
  105. package/rules/vue/docs/fix.md +25 -118
  106. package/rules/vue/js/docs/packages.md +25 -323
  107. package/rules/worktree/docs/fix.md +31 -150
  108. package/scripts/coverage-classify/docs/index.md +23 -209
  109. package/scripts/coverage-classify/docs/verdict-schema.md +14 -159
  110. package/scripts/dispatcher/docs/trace.md +35 -0
  111. package/scripts/docs/auto-rules.md +37 -361
  112. package/scripts/docs/lint-cli.md +12 -13
  113. package/scripts/docs/post-tool-use-fix.md +16 -15
  114. package/scripts/docs/skills-cli.md +26 -23
  115. package/scripts/docs/sync-claude-config.md +94 -34
  116. package/scripts/docs/worktree-cli.md +11 -34
  117. package/scripts/lib/docs/assert-project-root.md +14 -16
  118. package/scripts/lib/docs/changed-files.md +24 -139
  119. package/scripts/lib/docs/discover-check-rules-from-cursor.md +14 -146
  120. package/scripts/lib/docs/rule-predicates.md +20 -17
  121. package/scripts/lib/docs/run-rule-cli.md +14 -18
  122. package/scripts/lib/docs/run-rule.md +13 -20
  123. package/scripts/lib/docs/run-standard-rule.md +12 -15
  124. package/scripts/lib/docs/sync-gitignore-worktree.md +15 -18
  125. package/scripts/lib/rule-predicates.mjs +1 -1
  126. package/scripts/sync-claude-config.mjs +4 -1
  127. package/scripts/utils/docs/with-lock.md +19 -12
  128. package/scripts/utils/with-lock.mjs +4 -2
  129. package/skills/doc-aggregate/SKILL.md +2 -2
  130. package/skills/doc-aggregate/js/docgen-ignore.mjs +6 -6
  131. package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
  132. package/skills/doc-aggregate/js/docs/docgen-scan.md +78 -0
  133. package/skills/doc-files/.changes/260612-0031.md +5 -0
  134. package/skills/doc-files/.changes/260612-0036.md +5 -0
  135. package/skills/doc-files/.changes/260612-0114.md +5 -0
  136. package/skills/doc-files/SKILL.md +6 -6
  137. package/skills/fix/js/docs/llm-worker.md +17 -15
  138. package/skills/fix/js/docs/orchestrator.md +30 -23
  139. package/skills/fix/js/docs/t0.md +26 -16
  140. package/skills/start-check/js/docs/check.md +26 -22
  141. package/skills/taze/js/docs/diff.md +44 -20
  142. package/skills/doc-files/js/docs/docgen-prompts.md +0 -32
  143. package/skills/doc-files/js/docs/docgen-scan.md +0 -25
  144. package/skills/doc-files/js/docs/units-rs.md +0 -35
  145. /package/{skills → rules}/doc-files/js/docgen-crc.mjs +0 -0
  146. /package/{skills → rules}/doc-files/js/docgen-extract-anchors.mjs +0 -0
  147. /package/{skills → rules}/doc-files/js/docgen-files-batch.mjs +0 -0
  148. /package/{skills → rules}/doc-files/js/docgen-gen.mjs +0 -0
  149. /package/{skills → rules}/doc-files/js/docgen-prompts.mjs +0 -0
  150. /package/{skills → rules}/doc-files/js/units-js.mjs +0 -0
  151. /package/{skills → rules}/doc-files/js/units.mjs +0 -0
@@ -1,160 +1,29 @@
1
- # `no-process-chdir.mjs`
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
- ### `CHDIR_CALL_RE`
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
- ### Поведінка у crash-/inconsistent-сценаріях
12
+ Огляд
13
+ Файл шукає викликний паттерн process.chdir у тестових файлах. Він перевіряє, чи викликає будь-який тестовий файл process.chdir, і генерує повідомлення про порушення (test.mdc).
143
14
 
144
- - `readFile` з режимом `'utf8'` для бінарних/неіснуючих файлів кине помилку — обхідник `walkDir` має фільтрувати по типу файлу/доступу, але всередині `check` блоків `try/catch` немає, тож помилка спливе у викликача.
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
- - **Pass:** `Жоден з N тестових файлів не викликає process.chdir() (test.mdc)` (де `N` — кількість просканованих тест-файлів).
151
- - **Fail** (по одному рядку на кожен збіг): `path/to/file.test.mjs:123: process.chdir() у тесті заборонений — використовуй withTmpDir(async dir => …) + явні join(dir, …) + cwd: dir (test.mdc)`.
22
+ ## Публічний API
152
23
 
153
- ### Канонічна заміна `process.chdir` у тестах
24
+ check перевіряє, чи жоден файл з розширенням `*.test.{mjs,js}` не виконує `process.chdir(`. (test.mdc)
154
25
 
155
- Згідно з повідомленнями репортера і коментарем у заголовку:
26
+ ## Гарантії поведінки
156
27
 
157
- - Використовувати `withTmpDir(async dir => …)` зі `scripts/utils/test-helpers.mjs`.
158
- - У FS-операціях формувати шляхи через `join(dir, …)` явно.
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
- Модуль реалізує AST-based перевірку, яка забороняє передавати **relative-path**
6
- аргументи у функції модулів `node:fs` / `node:fs/promises` також у тестові
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
- Файл реалізує одну з перевірок з папки `npm/rules/test/js/`, що
230
- викликається через загальний механізм запуску правил (зазвичай
231
- `bun n-cursor rules` або еквівалентну команду). Загальний потік виклику
232
- `check(cwd)`:
15
+ ## Поведінка
233
16
 
234
- 1. Створення `reporter` через `createCheckReporter()`.
235
- 2. Завантаження `ignorePaths` з `.n-cursor.json`.
236
- 3. `walkDir(cwd, ..., ignorePaths)` рекурсивно проходить дерево;
237
- collector-callback відбирає лише файли, що задовольняють `isTestFile`.
238
- 4. Для кожного тестового файлу:
239
- - читається вміст;
240
- - `parseProgramOrNull` намагається спарсити; при невдачі файл
241
- мовчазно пропускається (`return []`);
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
- Типове використання — як check-функція у конвеєрі правил `test.mdc`
252
- (адже повідомлення містять згадку `(test.mdc, no-relative-fs-path)`),
253
- де `cwd` — корінь монорепо. Файл є частиною підкаталогу
254
- `npm/rules/test/js/` (правила, що відповідають `test.mdc`).
26
+ ## Публічний API
255
27
 
256
- Edge-cases:
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
- - Невалідний JS → `parseProgramOrNull` повертає `null` → файл пропускається
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
- Інтеграція з CI: повертає ненульовий код у разі порушень провалює
270
- відповідний крок CI; формат повідомлень містить `file:line: …`, що
271
- розпізнають IDE/CI-інтерфейси.
32
+ - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
33
+ - Кешує результати в межах одного прогону.
34
+ - Не звертається до мережі.