@7n/rules-lang-js 0.3.1 → 0.4.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.
Files changed (258) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +14 -4
  3. package/rules/bun/bunfig/bunfig.mdc +17 -0
  4. package/rules/bun/bunfig/bunfig.rego +29 -0
  5. package/rules/bun/bunfig/concern.json +9 -0
  6. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
  7. package/rules/bun/docs/index.md +11 -0
  8. package/rules/bun/layout/concern.json +16 -0
  9. package/rules/bun/layout/docs/fix-layout.md +29 -0
  10. package/rules/bun/layout/docs/main.md +34 -0
  11. package/rules/bun/layout/fix-layout.mjs +63 -0
  12. package/rules/bun/layout/layout.mdc +60 -0
  13. package/rules/bun/layout/main.mjs +53 -0
  14. package/rules/bun/licensee/concern.json +7 -0
  15. package/rules/bun/licensee/docs/fix-licensee.md +27 -0
  16. package/rules/bun/licensee/docs/index.md +12 -0
  17. package/rules/bun/licensee/docs/main.md +30 -0
  18. package/rules/bun/licensee/fix-licensee.mjs +31 -0
  19. package/rules/bun/licensee/main.mjs +68 -0
  20. package/rules/bun/lint-surface/concern.json +3 -0
  21. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  22. package/rules/bun/main.json +1 -0
  23. package/rules/bun/main.mdc +11 -0
  24. package/rules/bun/package_json/concern.json +9 -0
  25. package/rules/bun/package_json/docs/fix-package_json.md +28 -0
  26. package/rules/bun/package_json/docs/index.md +9 -0
  27. package/rules/bun/package_json/fix-package_json.mjs +311 -0
  28. package/rules/bun/package_json/package_json.mdc +14 -0
  29. package/rules/bun/package_json/package_json.rego +64 -0
  30. package/rules/bun/package_json/template/package.json.deny.json +4 -0
  31. package/rules/js/check/check.mdc +26 -0
  32. package/rules/js/check/concern.json +19 -0
  33. package/rules/js/check/docs/eslint-config.md +56 -0
  34. package/rules/js/check/docs/fix-check.md +48 -0
  35. package/rules/js/check/docs/index.md +13 -0
  36. package/rules/js/check/docs/main.md +48 -0
  37. package/rules/js/check/eslint-config.mjs +262 -0
  38. package/rules/js/check/fix-check.mjs +82 -0
  39. package/rules/js/check/main.mjs +310 -0
  40. package/rules/js/dep-policy/concern.json +4 -0
  41. package/rules/js/dep-policy/dep-policy.mdc +36 -0
  42. package/rules/js/dep-policy/docs/main.md +35 -0
  43. package/rules/js/dep-policy/main.mjs +99 -0
  44. package/rules/js/docs/index.md +11 -0
  45. package/rules/js/eslint/concern.json +8 -0
  46. package/rules/js/eslint/docs/fix-eslint.md +50 -0
  47. package/rules/js/eslint/docs/fix-worker.md +31 -0
  48. package/rules/js/eslint/docs/index.md +11 -0
  49. package/rules/js/eslint/docs/main.md +35 -0
  50. package/rules/js/eslint/fix-eslint.mjs +160 -0
  51. package/rules/js/eslint/fix-worker.mjs +139 -0
  52. package/rules/js/eslint/main.mjs +115 -0
  53. package/rules/js/file-extensions/concern.json +3 -0
  54. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  55. package/rules/js/jscpd_config/concern.json +11 -0
  56. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
  57. package/rules/js/jscpd_config/docs/index.md +9 -0
  58. package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
  59. package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
  60. package/rules/js/jscpd_config/jscpd_config.rego +44 -0
  61. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
  62. package/rules/js/jscpd_duplicates/concern.json +7 -0
  63. package/rules/js/jscpd_duplicates/docs/main.md +29 -0
  64. package/rules/js/jscpd_duplicates/main.mjs +69 -0
  65. package/rules/js/knip/concern.json +7 -0
  66. package/rules/js/knip/docs/main.md +31 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +89 -0
  69. package/rules/js/lint-findings/concern.json +3 -0
  70. package/rules/js/lint-findings/docs/main.md +40 -0
  71. package/rules/js/lint-findings/main.mjs +125 -0
  72. package/rules/js/main.json +1 -0
  73. package/rules/js/main.mdc +18 -0
  74. package/rules/js/package_json/concern.json +9 -0
  75. package/rules/js/package_json/docs/fix-package_json.md +25 -0
  76. package/rules/js/package_json/docs/index.md +9 -0
  77. package/rules/js/package_json/fix-package_json.mjs +3 -0
  78. package/rules/js/package_json/package_json.mdc +15 -0
  79. package/rules/js/package_json/package_json.rego +142 -0
  80. package/rules/js/package_json/template/package.json.snippet.json +6 -0
  81. package/rules/js/tooling/concern.json +3 -0
  82. package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
  83. package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
  84. package/rules/js/tooling/docs/main.md +53 -0
  85. package/rules/js/tooling/main.mjs +183 -0
  86. package/rules/js/utils_imports/concern.json +4 -0
  87. package/rules/js/utils_imports/docs/main.md +50 -0
  88. package/rules/js/utils_imports/main.mjs +185 -0
  89. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  90. package/rules/js/vscode_extensions/concern.json +11 -0
  91. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  92. package/rules/js/vscode_extensions/docs/index.md +11 -0
  93. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  94. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
  95. package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
  96. package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
  97. package/rules/js-bun-db/connection/concern.json +3 -0
  98. package/rules/js-bun-db/connection/connection.mdc +42 -0
  99. package/rules/js-bun-db/docs/index.md +11 -0
  100. package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
  101. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
  102. package/rules/js-bun-db/lib/docs/index.md +11 -0
  103. package/rules/js-bun-db/main.json +1 -0
  104. package/rules/js-bun-db/main.mdc +8 -0
  105. package/rules/js-bun-db/package_json/concern.json +9 -0
  106. package/rules/js-bun-db/package_json/package_json.mdc +31 -0
  107. package/rules/js-bun-db/package_json/package_json.rego +15 -0
  108. package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
  109. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  110. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  111. package/rules/js-bun-db/safety/concern.json +4 -0
  112. package/rules/js-bun-db/safety/docs/main.md +34 -0
  113. package/rules/js-bun-db/safety/main.mjs +430 -0
  114. package/rules/js-bun-db/safety/safety.mdc +458 -0
  115. package/rules/js-bun-redis/docs/index.md +11 -0
  116. package/rules/js-bun-redis/imports/concern.json +4 -0
  117. package/rules/js-bun-redis/imports/docs/main.md +36 -0
  118. package/rules/js-bun-redis/imports/imports.mdc +47 -0
  119. package/rules/js-bun-redis/imports/main.mjs +88 -0
  120. package/rules/js-bun-redis/lib/docs/index.md +11 -0
  121. package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
  122. package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
  123. package/rules/js-bun-redis/main.json +1 -0
  124. package/rules/js-bun-redis/main.mdc +8 -0
  125. package/rules/js-bun-redis/package_json/concern.json +9 -0
  126. package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
  127. package/rules/js-bun-redis/package_json/package_json.rego +15 -0
  128. package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
  129. package/rules/js-mssql/deps/concern.json +4 -0
  130. package/rules/js-mssql/deps/docs/main.md +33 -0
  131. package/rules/js-mssql/deps/main.mjs +297 -0
  132. package/rules/js-mssql/docs/index.md +11 -0
  133. package/rules/js-mssql/lib/docs/index.md +11 -0
  134. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
  135. package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
  136. package/rules/js-mssql/main.json +1 -0
  137. package/rules/js-mssql/main.mdc +144 -0
  138. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  139. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  140. package/rules/js-mssql/package_json/concern.json +9 -0
  141. package/rules/js-mssql/package_json/package_json.mdc +9 -0
  142. package/rules/js-mssql/package_json/package_json.rego +57 -0
  143. package/rules/js-run/configmap/concern.json +9 -0
  144. package/rules/js-run/configmap/configmap.mdc +37 -0
  145. package/rules/js-run/configmap/configmap.rego +21 -0
  146. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
  147. package/rules/js-run/docs/index.md +11 -0
  148. package/rules/js-run/jsconfig/concern.json +9 -0
  149. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
  150. package/rules/js-run/jsconfig/docs/index.md +9 -0
  151. package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
  152. package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
  153. package/rules/js-run/jsconfig/jsconfig.rego +59 -0
  154. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
  155. package/rules/js-run/lib/bunyan-imports.mjs +98 -0
  156. package/rules/js-run/lib/check-env-scan.mjs +338 -0
  157. package/rules/js-run/lib/conn-file-rules.mjs +214 -0
  158. package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
  159. package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
  160. package/rules/js-run/lib/docs/check-env-scan.md +438 -0
  161. package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
  162. package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
  163. package/rules/js-run/lib/docs/index.md +16 -0
  164. package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
  165. package/rules/js-run/lib/docs/temporal-scan.md +29 -0
  166. package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
  167. package/rules/js-run/lib/temporal-scan.mjs +52 -0
  168. package/rules/js-run/main.json +1 -0
  169. package/rules/js-run/main.mdc +16 -0
  170. package/rules/js-run/package_json/concern.json +9 -0
  171. package/rules/js-run/package_json/package_json.mdc +44 -0
  172. package/rules/js-run/package_json/package_json.rego +37 -0
  173. package/rules/js-run/package_json/template/package.json.deny.json +22 -0
  174. package/rules/js-run/project-structure/concern.json +3 -0
  175. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  176. package/rules/js-run/runtime/concern.json +12 -0
  177. package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
  178. package/rules/js-run/runtime/docs/main.md +35 -0
  179. package/rules/js-run/runtime/fix-runtime.mjs +46 -0
  180. package/rules/js-run/runtime/main.mjs +496 -0
  181. package/rules/js-run/runtime/runtime.mdc +184 -0
  182. package/rules/js-run/scope/concern.json +3 -0
  183. package/rules/js-run/scope/scope.mdc +11 -0
  184. package/rules/npm-module/docs/index.md +11 -0
  185. package/rules/npm-module/emit_types_config/concern.json +9 -0
  186. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
  187. package/rules/npm-module/emit_types_config/docs/index.md +9 -0
  188. package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
  189. package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
  190. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
  191. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  192. package/rules/npm-module/header_doc_pointer/concern.json +5 -0
  193. package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
  194. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
  195. package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
  196. package/rules/npm-module/main.json +1 -0
  197. package/rules/npm-module/main.mdc +36 -0
  198. package/rules/npm-module/npm_package_json/concern.json +9 -0
  199. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
  200. package/rules/npm-module/npm_package_json/docs/index.md +9 -0
  201. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
  202. package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
  203. package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
  204. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
  205. package/rules/npm-module/package_structure/concern.json +5 -0
  206. package/rules/npm-module/package_structure/docs/main.md +37 -0
  207. package/rules/npm-module/package_structure/main.mjs +448 -0
  208. package/rules/npm-module/package_structure/package_structure.mdc +63 -0
  209. package/rules/npm-module/root_package_json/concern.json +9 -0
  210. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
  211. package/rules/npm-module/root_package_json/docs/index.md +9 -0
  212. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
  213. package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
  214. package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
  215. package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
  216. package/rules/npm-module/rule_meta/concern.json +8 -0
  217. package/rules/npm-module/rule_meta/docs/main.md +35 -0
  218. package/rules/npm-module/rule_meta/main.mjs +119 -0
  219. package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
  220. package/rules/npm-module/skill_meta/concern.json +5 -0
  221. package/rules/npm-module/skill_meta/docs/main.md +149 -0
  222. package/rules/npm-module/skill_meta/main.mjs +91 -0
  223. package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
  224. package/rules/tool-surface/docs/index.md +11 -0
  225. package/rules/tool-surface/main.json +6 -0
  226. package/rules/tool-surface/main.mdc +72 -0
  227. package/rules/vue/composition-api/composition-api.mdc +82 -0
  228. package/rules/vue/composition-api/concern.json +3 -0
  229. package/rules/vue/docs/index.md +11 -0
  230. package/rules/vue/lib/docs/index.md +11 -0
  231. package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
  232. package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
  233. package/rules/vue/main.json +1 -0
  234. package/rules/vue/main.mdc +18 -0
  235. package/rules/vue/nheader-layout/concern.json +3 -0
  236. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  237. package/rules/vue/package_json/concern.json +9 -0
  238. package/rules/vue/package_json/package_json.mdc +30 -0
  239. package/rules/vue/package_json/package_json.rego +140 -0
  240. package/rules/vue/packages/concern.json +6 -0
  241. package/rules/vue/packages/docs/index.md +11 -0
  242. package/rules/vue/packages/docs/main.md +35 -0
  243. package/rules/vue/packages/main.mjs +575 -0
  244. package/rules/vue/packages/packages.mdc +56 -0
  245. package/rules/vue/quasar-ui/concern.json +3 -0
  246. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  247. package/rules/vue/structure/concern.json +3 -0
  248. package/rules/vue/structure/structure.mdc +101 -0
  249. package/rules/vue/testing/concern.json +3 -0
  250. package/rules/vue/testing/testing.mdc +40 -0
  251. package/rules/vue/tfm-translations/concern.json +7 -0
  252. package/rules/vue/tfm-translations/docs/main.md +29 -0
  253. package/rules/vue/tfm-translations/main.mjs +55 -0
  254. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  255. package/rules/vue/vite-config/concern.json +3 -0
  256. package/rules/vue/vite-config/vite-config.mdc +153 -0
  257. package/rules/vue/vite-env/concern.json +3 -0
  258. package/rules/vue/vite-env/vite-env.mdc +61 -0
@@ -0,0 +1,334 @@
1
+ ---
2
+ type: JS Module
3
+ title: promise-settimeout-scan.mjs
4
+ resource: plugins/lang-js/rules/js-run/lib/promise-settimeout-scan.mjs
5
+ docgen:
6
+ crc: 83b41b33
7
+ ---
8
+
9
+ Модуль `promise-settimeout-scan.mjs` — це AST-сканер, який виявляє у вихідному коді JavaScript/TypeScript антипаттерн «обгортка `setTimeout` у `new Promise`» виду:
10
+
11
+ ```js
12
+ new Promise(resolve => setTimeout(resolve, ms))
13
+ // або
14
+ await new Promise(resolve => setTimeout(resolve, ms))
15
+ ```
16
+
17
+ Згідно з правилом `js-run.mdc` (секція «Паузи через `setTimeout`») такий код потрібно замінити на ідіоматичний імпорт із `node:timers/promises`:
18
+
19
+ ```js
20
+ import { setTimeout as sleep } from 'node:timers/promises'
21
+
22
+ await sleep(ms)
23
+ ```
24
+
25
+ Сканер працює **структурно** (по AST), без regex-у по тілу: шукає `NewExpression`, у якого callee — Identifier `Promise`, а єдиний аргумент — функція з одним параметром-resolve, тіло якої — єдиний виклик `setTimeout(<resolve>, ms)`. Перший аргумент `setTimeout` мусить бути «голим» resolve — або сам ідентифікатор, або тривіальна безпараметрична обгортка `() => resolve()` / `function () { resolve() }` без жодних переданих аргументів. Якщо у виклик `resolve` передається значення — це вже не «чиста пауза», і паттерн **не** вмикається.
26
+
27
+ Сканер **толерантний до синтаксичних помилок**: якщо файл не парситься, повертається порожній список (узгоджено з рештою AST-сканерів проєкту — спочатку треба полагодити синтаксис, а вже потім ловити структурні порушення).
28
+
29
+ ## Експорти / API
30
+
31
+ | Експорт | Тип | Призначення |
32
+ | ----------------------------------- | ---------- | ---------------------------------------------------------------------------------- |
33
+ | `findPromiseSetTimeoutInText` | `function` | Знаходить усі входження антипаттерну в тексті, повертає масив `{ line, snippet }`. |
34
+ | `isPromiseSetTimeoutScanSourceFile` | `function` | Фільтр по відносному шляху: чи варто взагалі сканувати цей файл (за розширенням). |
35
+
36
+ Внутрішні (не експортовані) хелпери, які формують ядро аналізу:
37
+
38
+ - `isBareResolveCallback(arg, paramName)` — перевірка «чистоти» першого аргументу `setTimeout`.
39
+ - `extractSingleCallExpression(body)` — витягнення єдиного `CallExpression` з тіла функції.
40
+ - `isPromiseSetTimeoutDelay(node)` — головний предикат паттерну на рівні `NewExpression`.
41
+ - `walkAst(node, visit)` — простий рекурсивний обхід AST.
42
+
43
+ Константа модульного рівня:
44
+
45
+ - `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/` — regex розширень JS/TS-сім'ї (`.js`, `.mjs`, `.cjs`, `.ts`, `.mts`, `.cts`, `.jsx`, `.tsx`).
46
+
47
+ ## Функції
48
+
49
+ ### `findPromiseSetTimeoutInText(content, virtualPath = 'scan.ts')`
50
+
51
+ **Призначення.** Публічна точка входу: парсить вихідний код, обходить AST і збирає всі позиції, де знайдено антипаттерн «`new Promise(... setTimeout ...)`».
52
+
53
+ **Сигнатура.**
54
+
55
+ ```js
56
+ export function findPromiseSetTimeoutInText(
57
+ content: string,
58
+ virtualPath?: string
59
+ ): { line: number, snippet: string }[]
60
+ ```
61
+
62
+ **Параметри.**
63
+
64
+ - `content: string` — повний текст файлу для сканування.
65
+ - `virtualPath: string` (за замовчуванням `'scan.ts'`) — віртуальний шлях, який передається у `parseProgramOrNull` для вибору мови парсера (TS/JSX тощо). Не використовується для read/write-у на диск; впливає лише на режим парсингу.
66
+
67
+ **Повертає.** Масив об'єктів `{ line: number, snippet: string }` — по одному запису на кожне виявлене порушення:
68
+
69
+ - `line` — 1-based номер рядка, де починається `NewExpression` (через `offsetToLine`).
70
+ - `snippet` — нормалізований текст самого `new Promise(...)`-виразу (через `normalizeSnippet`).
71
+
72
+ Якщо AST не побудувався (синтаксична помилка), повертає порожній масив `[]`.
73
+
74
+ **Side effects.** Чиста функція — жодного I/O, читання env, мутацій глобального стану. Усі дані повертаються через return value.
75
+
76
+ ---
77
+
78
+ ### `isPromiseSetTimeoutScanSourceFile(relativePath)`
79
+
80
+ **Призначення.** Фільтр для зовнішнього раннера/обхідника файлів: чи варто взагалі парсити цей файл.
81
+
82
+ **Сигнатура.**
83
+
84
+ ```js
85
+ export function isPromiseSetTimeoutScanSourceFile(
86
+ relativePath: string
87
+ ): boolean
88
+ ```
89
+
90
+ **Параметри.**
91
+
92
+ - `relativePath: string` — відносний шлях до файлу (наприклад, `src/utils/sleep.ts`).
93
+
94
+ **Повертає.** `true`, якщо:
95
+
96
+ 1. Розширення збігається з `SOURCE_FILE_RE` (JS/TS-сім'я), **та**
97
+ 2. Шлях **не** закінчується на `.d.ts` (декларації типів пропускаються).
98
+
99
+ Інакше — `false`.
100
+
101
+ **Side effects.** Немає.
102
+
103
+ ---
104
+
105
+ ### `isBareResolveCallback(arg, paramName)` (внутрішня)
106
+
107
+ **Призначення.** Перевіряє, що перший аргумент `setTimeout` — це або сам ідентифікатор `resolve`, або тривіальна безпараметрична обгортка, яка викликає `resolve()` без значення.
108
+
109
+ **Сигнатура.**
110
+
111
+ ```js
112
+ function isBareResolveCallback(
113
+ arg: Record<string, unknown> | null | undefined,
114
+ paramName: string
115
+ ): boolean
116
+ ```
117
+
118
+ **Параметри.**
119
+
120
+ - `arg` — AST-вузол першого аргументу виклику `setTimeout`.
121
+ - `paramName` — ім'я параметра-resolve у тіла-функції `Promise` (зазвичай `'resolve'`, але приймається будь-яке ім'я-Identifier).
122
+
123
+ **Повертає.** `true`, якщо аргумент — це:
124
+
125
+ - `Identifier` з іменем `paramName` (наприклад, `setTimeout(resolve, ms)`); **або**
126
+ - `ArrowFunctionExpression` / `FunctionExpression` без параметрів, тіло якого — рівно один `CallExpression`, callee — `Identifier paramName`, а список аргументів виклику — порожній (наприклад, `() => resolve()` або `function () { resolve() }`).
127
+
128
+ В інших випадках — `false`. Зокрема, якщо у виклик `resolve(x)` передається значення — це **не** «чиста пауза» (бо результат `await` був би `x`, а не `undefined`), і антипаттерн **не** фіксується.
129
+
130
+ **Side effects.** Немає.
131
+
132
+ ---
133
+
134
+ ### `extractSingleCallExpression(body)` (внутрішня)
135
+
136
+ **Призначення.** Витягує єдиний `CallExpression` з тіла функції — як у концизній стрілковій формі (`() => foo()`), так і в блоковій з рівно одним стейтментом (`() => { foo() }`).
137
+
138
+ **Сигнатура.**
139
+
140
+ ```js
141
+ function extractSingleCallExpression(
142
+ body: unknown
143
+ ): Record<string, unknown> | null
144
+ ```
145
+
146
+ **Параметри.**
147
+
148
+ - `body` — AST-вузол тіла функції (може бути `CallExpression`, `BlockStatement` або щось інше).
149
+
150
+ **Повертає.** AST-вузол `CallExpression`, якщо:
151
+
152
+ - `body.type === 'CallExpression'` — повертає сам body; **або**
153
+ - `body.type === 'BlockStatement'`, масив `body.body` містить рівно один елемент, цей елемент — `ExpressionStatement`, а його `expression.type === 'CallExpression'` — повертає цей `expression`.
154
+
155
+ Інакше — `null`.
156
+
157
+ **Side effects.** Немає.
158
+
159
+ ---
160
+
161
+ ### `isPromiseSetTimeoutDelay(node)` (внутрішня)
162
+
163
+ **Призначення.** Головний предикат паттерну: чи це `NewExpression` виду `new Promise(<resolve> => setTimeout(<resolve>, ms))`.
164
+
165
+ **Сигнатура.**
166
+
167
+ ```js
168
+ function isPromiseSetTimeoutDelay(
169
+ node: Record<string, unknown> | null | undefined
170
+ ): boolean
171
+ ```
172
+
173
+ **Параметри.**
174
+
175
+ - `node` — довільний AST-вузол (під час обходу через `walkAst`).
176
+
177
+ **Повертає.** `true`, якщо всі наступні умови виконані:
178
+
179
+ 1. `node.type === 'NewExpression'`;
180
+ 2. `node.callee` — `Identifier` з іменем `'Promise'` (саме глобальний/локальний ідентифікатор, без member-доступу `foo.Promise`);
181
+ 3. `node.arguments.length === 1`;
182
+ 4. Цей єдиний аргумент — `ArrowFunctionExpression` або `FunctionExpression` з мінімум одним параметром;
183
+ 5. Перший параметр — `Identifier` (запам'ятовується його `name` як ім'я resolve);
184
+ 6. Тіло функції містить рівно один `CallExpression` (через `extractSingleCallExpression`);
185
+ 7. Callee цього виклику — `Identifier` з іменем `'setTimeout'` (джерело — глобальне, з `node:timers`, з `globalThis` — для сканера не важливо);
186
+ 8. У виклику `setTimeout` хоча б один аргумент;
187
+ 9. Перший аргумент `setTimeout` — «голий» resolve (через `isBareResolveCallback`).
188
+
189
+ Якщо будь-яка умова порушена — повертає `false`.
190
+
191
+ **Side effects.** Немає.
192
+
193
+ ---
194
+
195
+ ### `walkAst(node, visit)` (внутрішня)
196
+
197
+ **Призначення.** Простий generic-обхід AST: рекурсивно спускається по всіх властивостях і елементах масивів, викликаючи `visit` для кожного об'єкта-вузла, який має поле `type` (тобто справжнього AST-вузла, а не службового мета-обʼєкта).
198
+
199
+ **Сигнатура.**
200
+
201
+ ```js
202
+ function walkAst(
203
+ node: unknown,
204
+ visit: (n: Record<string, unknown>) => void
205
+ ): void
206
+ ```
207
+
208
+ **Параметри.**
209
+
210
+ - `node` — корінь або під-вузол (Program, окремий вузол, масив, скаляр — все підтримується).
211
+ - `visit` — колбек, який отримує кожен AST-вузол із полем `type`. Викликається **до** спуску в дочірні поля (pre-order).
212
+
213
+ **Поведінка.**
214
+
215
+ - `null`/примітиви — пропускаються;
216
+ - Масиви — обходяться поелементно;
217
+ - Об'єкти з `typeof node.type === 'string'` — спершу передаються у `visit`, потім обходяться їхні поля;
218
+ - Поле з ключем `'parent'` пропускається (захист від циклів у деяких AST-моделях, де є зворотні посилання);
219
+ - Інші поля-об'єкти — рекурсивно обходяться.
220
+
221
+ **Повертає.** `undefined`.
222
+
223
+ **Side effects.** Викликає `visit` зовнішнього коду — single side effect виноситься назовні через колбек.
224
+
225
+ ## Залежності
226
+
227
+ ### Зовнішні (relative imports)
228
+
229
+ Із `../../../scripts/utils/ast-scan-utils.mjs`:
230
+
231
+ - `parseProgramOrNull(content, virtualPath)` — парсить вихідний код у Program-вузол AST з урахуванням мови (вибір TS/JS/JSX за розширенням у `virtualPath`); повертає `null` при синтаксичних помилках.
232
+ - `offsetToLine(content, offset)` — перетворює byte/char offset у 1-based номер рядка.
233
+ - `normalizeSnippet(text)` — нормалізує текст сніпета (стискання пробілів/переносів) для зручного виводу у звітах.
234
+
235
+ ### Глобальні
236
+
237
+ - `RegExp` (`SOURCE_FILE_RE`), `Array.isArray`, `Object.keys`, `String.prototype.endsWith`, `String.prototype.slice`, `Array.prototype.push` — стандартні API ES.
238
+
239
+ ### Без рантайм-залежностей
240
+
241
+ Жодних `node:`-вбудованих модулів, жодних npm-пакетів, жодних звернень до файлової системи, env або мережі.
242
+
243
+ ## Потік виконання / Використання
244
+
245
+ ### Типовий сценарій (з раннера/перевіряча)
246
+
247
+ ```js
248
+ import { findPromiseSetTimeoutInText, isPromiseSetTimeoutScanSourceFile } from './promise-settimeout-scan.mjs'
249
+ import { readFile } from 'node:fs/promises'
250
+
251
+ /**
252
+ *
253
+ */
254
+ async function scanRepo(files) {
255
+ const violations = []
256
+ for (const relPath of files) {
257
+ if (!isPromiseSetTimeoutScanSourceFile(relPath)) continue
258
+ const content = await readFile(relPath, 'utf8')
259
+ const hits = findPromiseSetTimeoutInText(content, relPath)
260
+ for (const hit of hits) {
261
+ violations.push({ file: relPath, line: hit.line, snippet: hit.snippet })
262
+ }
263
+ }
264
+ return violations
265
+ }
266
+ ```
267
+
268
+ ### Алгоритм всередині `findPromiseSetTimeoutInText`
269
+
270
+ 1. **Парсинг.** `parseProgramOrNull(content, virtualPath)` → якщо `null` (синтаксична помилка), функція одразу повертає `[]`.
271
+ 2. **Ініціалізація.** Створюється порожній масив `out` для накопичення знахідок.
272
+ 3. **Обхід AST.** `walkAst(program, visit)` рекурсивно проходить весь Program-вузол.
273
+ 4. **Перевірка паттерну.** Для кожного AST-вузла з полем `type` викликається `visit`, який:
274
+ - Перевіряє `isPromiseSetTimeoutDelay(node)`;
275
+ - Якщо `true` — пушить у `out` об'єкт `{ line, snippet }`, де `line` обчислюється з `node.start` через `offsetToLine`, а `snippet` — з `content.slice(node.start, node.end)` через `normalizeSnippet`.
276
+ 5. **Повернення.** Масив `out` повертається як результат (може бути порожнім, якщо порушень немає).
277
+
278
+ ### Що сканер ловить (приклади позитивних спрацювань)
279
+
280
+ ```js
281
+ new Promise(resolve => setTimeout(resolve, 1000))
282
+ await new Promise(r => setTimeout(r, ms))
283
+ new Promise(resolve => setTimeout(() => resolve(), 500))
284
+ new Promise(function (resolve) {
285
+ setTimeout(resolve, 200)
286
+ })
287
+ new Promise(resolve => {
288
+ setTimeout(function () {
289
+ resolve()
290
+ }, 100)
291
+ })
292
+ ```
293
+
294
+ ### Що сканер свідомо **не** ловить (негативні приклади)
295
+
296
+ - `new Promise(resolve => setTimeout(() => resolve(value), ms))` — у `resolve` передається значення; це не «чиста пауза».
297
+ - `new Promise((resolve, reject) => setTimeout(() => doStuff().then(resolve, reject), ms))` — у тілі більше ніж один call або інший callee.
298
+ - `new MyPromise(resolve => setTimeout(resolve, ms))` — callee — не `Promise`.
299
+ - `globalThis.Promise` як callee (MemberExpression) — не Identifier.
300
+ - Файли з синтаксичними помилками — `parseProgramOrNull` повертає `null`, сканер віддає `[]`.
301
+ - Файли з розширенням `.d.ts` — фільтруються `isPromiseSetTimeoutScanSourceFile`.
302
+
303
+ ### Інтеграція з правилом `js-run.mdc`
304
+
305
+ Цей сканер — частина перевірок правила js-run, секція «Паузи через `setTimeout`». Він використовується check-скриптом правила для збору списку порушень, які потім виводяться користувачу зі вказівкою замінити обгортку на `setTimeout` із `node:timers/promises`. Сам файл `promise-settimeout-scan.mjs` не виконує жодних дій з виправлення — лише детектує.
306
+
307
+ ## Rebuild Test
308
+
309
+ Перевірка контрактів модуля (умоглядно, без запуску):
310
+
311
+ 1. **`isPromiseSetTimeoutScanSourceFile`:**
312
+ - `'src/a.ts'` → `true`; `'src/a.tsx'` → `true`; `'src/a.mjs'` → `true`; `'src/a.cjs'` → `true`.
313
+ - `'src/a.d.ts'` → `false` (декларації пропускаються).
314
+ - `'src/a.md'` / `'src/a.json'` → `false` (інше розширення).
315
+
316
+ 2. **`findPromiseSetTimeoutInText` — позитивний кейс:**
317
+ - Вхід: `const s = new Promise(r => setTimeout(r, 100))`.
318
+ - Очікувано: масив з одним записом, `line === 1`, `snippet` містить нормалізований текст `new Promise(r => setTimeout(r, 100))`.
319
+
320
+ 3. **`findPromiseSetTimeoutInText` — негативний кейс (передача значення):**
321
+ - Вхід: `new Promise(r => setTimeout(() => r(42), 10))`.
322
+ - Очікувано: `[]` (бо `isBareResolveCallback` відсіває виклики з аргументами).
323
+
324
+ 4. **`findPromiseSetTimeoutInText` — синтаксична помилка:**
325
+ - Вхід: `const x = (` (незакритий вираз).
326
+ - Очікувано: `[]` (бо `parseProgramOrNull` повертає `null`).
327
+
328
+ 5. **`findPromiseSetTimeoutInText` — інший callee:**
329
+ - Вхід: `new MyPromise(r => setTimeout(r, 1))`.
330
+ - Очікувано: `[]` (бо `node.callee.name !== 'Promise'`).
331
+
332
+ 6. **`isPromiseSetTimeoutDelay` — більше одного стейтменту в блоці:**
333
+ - Тіло Promise-функції: `{ doSomething(); setTimeout(resolve, 10) }`.
334
+ - Очікувано: `false` (бо `extractSingleCallExpression` вимагає рівно один стейтмент).
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: temporal-scan.mjs
4
+ resource: plugins/lang-js/rules/js-run/lib/temporal-scan.mjs
5
+ docgen:
6
+ crc: 37bae946
7
+ ---
8
+
9
+ Цей файл є частиною системи, яка сканує код Bun workspace на наявність використання ключового слова `Temporal`. Він запобігає використанню `Temporal` у backend-коді, оскільки Bun 1.3.x ще не має глобального `Temporal`, та охоплює сценарії з імпортом та polyfill. Це забезпечує відповідність коду поточним вимогам Bun runtime щодо обробки часу.
10
+
11
+ ## Поведінка
12
+
13
+ Знаходить використання identifier `Temporal` у тексті. Повертає список рядків та фрагментів коду, де зустрічається `Temporal`.
14
+ Чи сканувати файл за розширенням (JS/TS-сім'я, виключно з `.d.ts`). Повертає `true`, якщо файл має відповідне розширення, і не є файлом `.d.ts`.
15
+
16
+ ## Публічний API
17
+
18
+ - findTemporalUsageInText — Знаходить згадки про `Temporal` у тексті.
19
+ - isTemporalScanSourceFile — Визначає, чи потрібно сканувати файл за розширенням (JavaScript/TypeScript або `.d.ts`).
20
+
21
+ ## Гарантії поведінки
22
+
23
+ - Функція `findTemporalUsageInText` повертає `true` лише якщо знайде identifier `Temporal` у наданому тексті.
24
+ - Функція `findTemporalUsageInText` повертає `false` якщо identifier `Temporal` не знайдено.
25
+ - Функція `isTemporalScanSourceFile` повертає `true` якщо у файлі є identifier `Temporal`.
26
+ - Функція `isTemporalScanSourceFile` повертає `false` якщо у файлі немає identifier `Temporal`.
27
+ - Результат роботи `findTemporalUsageInText` не гарантує, що identifier `Temporal` використовується правильно.
28
+ - Результат роботи `isTemporalScanSourceFile` не гарантує, що використання `Temporal` є допустимим.
29
+ - Кеш не використовується.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Знаходить паттерн `new Promise(resolve => setTimeout(resolve, ms))` (з `await` чи без)
3
+ * у джерелах — таку обгортку треба замінити на `setTimeout` з `node:timers/promises`
4
+ * згідно з js-run.mdc, секція «Паузи через setTimeout».
5
+ *
6
+ * Семантика — структурна (без regex по тілу): `NewExpression` з ідентифікатор-callee `Promise`
7
+ * і єдиним аргументом-функцією, тіло якої — виклик `setTimeout(<resolve>, ms)`. Перший
8
+ * аргумент `setTimeout` має передавати `resolve` напряму або тривіально загорнутим у
9
+ * безпараметричну функцію `() => resolve()` / `function () { resolve() }` без жодних
10
+ * аргументів — інакше це не «чиста пауза», і паттерн не вмикається.
11
+ *
12
+ * Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках повертається
13
+ * порожній результат (як інші сканери — спочатку треба полагодити синтаксис).
14
+ */
15
+ import { normalizeSnippet, offsetToLine, parseProgramOrNull } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
16
+
17
+ const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/
18
+
19
+ /**
20
+ * Чи аргумент, який передають у `setTimeout`, — це «голий» виклик `resolve`
21
+ * (тобто сам ідентифікатор або `() => resolve()` без аргументів).
22
+ * @param {Record<string, unknown> | null | undefined} arg AST-вузол першого аргументу `setTimeout`
23
+ * @param {string} paramName ім'я параметра-resolve у тіла-функції Promise
24
+ * @returns {boolean} `true`, якщо це чиста передача resolve без значення
25
+ */
26
+ function isBareResolveCallback(arg, paramName) {
27
+ if (!arg || typeof arg !== 'object') return false
28
+ if (arg.type === 'Identifier' && arg.name === paramName) return true
29
+ if (arg.type !== 'ArrowFunctionExpression' && arg.type !== 'FunctionExpression') return false
30
+ if ((arg.params?.length ?? 0) !== 0) return false
31
+ const callExpr = extractSingleCallExpression(arg.body)
32
+ if (!callExpr) return false
33
+ if (callExpr.callee?.type !== 'Identifier' || callExpr.callee.name !== paramName) return false
34
+ return !Array.isArray(callExpr.arguments) || callExpr.arguments.length === 0
35
+ }
36
+
37
+ /**
38
+ * Якщо тіло функції — рівно один `CallExpression` (концизне `() => foo()` або
39
+ * `{ foo() }` без інших стейтментів), повертає його. Інакше — `null`.
40
+ * @param {unknown} body тіло функції з AST
41
+ * @returns {Record<string, unknown> | null} AST-вузол `CallExpression` або `null`
42
+ */
43
+ function extractSingleCallExpression(body) {
44
+ if (!body || typeof body !== 'object') return null
45
+ if (body.type === 'CallExpression') return body
46
+ if (body.type !== 'BlockStatement') return null
47
+ if (!Array.isArray(body.body) || body.body.length !== 1) return null
48
+ const stmt = body.body[0]
49
+ if (!stmt || stmt.type !== 'ExpressionStatement') return null
50
+ const expr = stmt.expression
51
+ return expr?.type === 'CallExpression' ? expr : null
52
+ }
53
+
54
+ /**
55
+ * Чи це `NewExpression` виду `new Promise(<resolve> => setTimeout(<resolve>, ms))`.
56
+ * Параметр-resolve має бути простим Identifier; setTimeout — глобальним викликом
57
+ * за іменем (з будь-якого джерела — node:timers, global, тощо: значення для нас має
58
+ * лише структурний паттерн).
59
+ * @param {Record<string, unknown> | null | undefined} node AST-вузол
60
+ * @returns {boolean} `true`, якщо це проблемний паттерн «обгортки таймера у Promise»
61
+ */
62
+ function isPromiseSetTimeoutDelay(node) {
63
+ if (!node || node.type !== 'NewExpression') return false
64
+ if (node.callee?.type !== 'Identifier' || node.callee.name !== 'Promise') return false
65
+ if (!Array.isArray(node.arguments) || node.arguments.length !== 1) return false
66
+ const fn = node.arguments[0]
67
+ if (!fn || (fn.type !== 'ArrowFunctionExpression' && fn.type !== 'FunctionExpression')) return false
68
+ if (!Array.isArray(fn.params) || fn.params.length === 0) return false
69
+ const firstParam = fn.params[0]
70
+ if (!firstParam || firstParam.type !== 'Identifier') return false
71
+ const setTimeoutCall = extractSingleCallExpression(fn.body)
72
+ if (!setTimeoutCall) return false
73
+ if (setTimeoutCall.callee?.type !== 'Identifier' || setTimeoutCall.callee.name !== 'setTimeout') return false
74
+ if (!Array.isArray(setTimeoutCall.arguments) || setTimeoutCall.arguments.length < 1) return false
75
+ return isBareResolveCallback(setTimeoutCall.arguments[0], firstParam.name)
76
+ }
77
+
78
+ /**
79
+ * Простий рекурсивний обхід AST: заходимо в усі об'єкти/масиви, щоб знайти `NewExpression`.
80
+ * @param {unknown} node корінь або під-вузол AST
81
+ * @param {(n: Record<string, unknown>) => void} visit виклик для кожного об'єкта-вузла з `type`
82
+ * @returns {void}
83
+ */
84
+ function walkAst(node, visit) {
85
+ if (!node || typeof node !== 'object') return
86
+ if (Array.isArray(node)) {
87
+ for (const item of node) walkAst(item, visit)
88
+ return
89
+ }
90
+ if (typeof node.type === 'string') {
91
+ visit(node)
92
+ }
93
+ for (const [key, v] of Object.entries(node)) {
94
+ if (key === 'parent') continue
95
+ if (v && typeof v === 'object') walkAst(v, visit)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Знаходить усі `new Promise(resolve => setTimeout(resolve, ms))` у тексті.
101
+ * @param {string} content вихідний код
102
+ * @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/foo.ts`)
103
+ * @returns {{ line: number, snippet: string }[]} список порушень
104
+ */
105
+ export function findPromiseSetTimeoutInText(content, virtualPath = 'scan.ts') {
106
+ const program = parseProgramOrNull(content, virtualPath)
107
+ if (!program) return []
108
+ /** @type {{ line: number, snippet: string }[]} */
109
+ const out = []
110
+ walkAst(program, node => {
111
+ if (!isPromiseSetTimeoutDelay(node)) return
112
+ out.push({
113
+ line: offsetToLine(content, node.start),
114
+ snippet: normalizeSnippet(content.slice(node.start, node.end))
115
+ })
116
+ })
117
+ return out
118
+ }
119
+
120
+ /**
121
+ * Чи сканувати цей файл за розширенням (JS/TS-сім'я, виключно з `.d.ts`).
122
+ * @param {string} relativePath відносний шлях до файлу
123
+ * @returns {boolean} `true`, якщо розширення підходить для сканування
124
+ */
125
+ export function isPromiseSetTimeoutScanSourceFile(relativePath) {
126
+ if (!SOURCE_FILE_RE.test(relativePath)) return false
127
+ return !relativePath.endsWith('.d.ts')
128
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * AST-сканер заборони `Temporal` у Bun runtime-коді.
3
+ *
4
+ * Bun 1.3.x ще не має глобального `Temporal`, тому правило js-run забороняє
5
+ * будь-який identifier `Temporal` у backend workspace-коді. Заборона свідомо
6
+ * охоплює polyfill/import-сценарії: у цьому репозиторії канон для часу лишається
7
+ * через `Date` або ін'єкцію timestamp у чисті функції.
8
+ */
9
+ import {
10
+ normalizeSnippet,
11
+ offsetToLine,
12
+ parseProgramOrNull,
13
+ walkAstWithAncestors
14
+ } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
15
+
16
+ const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
17
+
18
+ /**
19
+ * Знаходить використання identifier `Temporal` у тексті.
20
+ * @param {string} content вихідний код
21
+ * @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/foo.ts`)
22
+ * @returns {{ line: number, snippet: string }[]} список порушень
23
+ */
24
+ export function findTemporalUsageInText(content, virtualPath = 'scan.ts') {
25
+ const program = parseProgramOrNull(content, virtualPath)
26
+ if (!program) return []
27
+ /** @type {{ line: number, snippet: string }[]} */
28
+ const out = []
29
+ /** @type {Set<string>} */
30
+ const seen = new Set()
31
+ walkAstWithAncestors(program, [], node => {
32
+ if (node.type !== 'Identifier' || node.name !== 'Temporal') return
33
+ const key = `${node.start}:${node.end}`
34
+ if (seen.has(key)) return
35
+ seen.add(key)
36
+ out.push({
37
+ line: offsetToLine(content, node.start),
38
+ snippet: normalizeSnippet(content.slice(node.start, node.end))
39
+ })
40
+ })
41
+ return out
42
+ }
43
+
44
+ /**
45
+ * Чи сканувати цей файл за розширенням (JS/TS-сім'я, виключно з `.d.ts`).
46
+ * @param {string} relativePath відносний шлях до файлу
47
+ * @returns {boolean} `true`, якщо розширення підходить для сканування
48
+ */
49
+ export function isTemporalScanSourceFile(relativePath) {
50
+ if (!SOURCE_FILE_RE.test(relativePath)) return false
51
+ return !relativePath.endsWith('.d.ts')
52
+ }
@@ -0,0 +1 @@
1
+ { "auto": { "predicate": "nestedPackageWithoutVite" } }
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Це правила для backend проектів на JavaScript/Node.js, сюди входять і job і WEB сервери.
3
+ globs: "**/package.json,**/jsconfig.json,**/src/**/*.{js,mjs,cjs,ts,tsx}"
4
+ alwaysApply: false
5
+ version: '1.12'
6
+ ---
7
+
8
+ Правило охоплює backend Node.js workspace-пакети (jobs, GraphQL/HTTP-сервери, CLI) — визначення меж застосування, вимоги до runtime, структуру проекту, конфігурацію, логування, підключення до БД/GraphQL і безпечне використання env-змінних.
9
+
10
+ ## Швидкий gate через conftest
11
+
12
+ Rego-пакети, які запускає `npx @7n/rules fix js-run` / `npx @7n/rules check`:
13
+
14
+ - [jsconfig.json.snippet.json](./jsconfig/template/jsconfig.json.snippet.json)
15
+ - [configmap.yaml.contains.yml](./configmap/template/configmap.yaml.contains.yml)
16
+ - [package.json.deny.json](./package_json/template/package.json.deny.json)
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "policy": {
5
+ "files": {
6
+ "walkGlob": "**/package.json"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,44 @@
1
+ ## Rego-gate: package.json (залежності та scripts)
2
+
3
+ Rego-пакет: `js-run.package_json`
4
+
5
+ **Цільові файли:** `package.json` у backend workspace-пакетах (без `vite` у `devDependencies`)
6
+
7
+ Перевіряє три класи порушень за deny-списком із template:
8
+
9
+ Канон deny-списку: [package.json.deny.json](./template/package.json.deny.json)
10
+
11
+ **1. Заборонені залежності** (`dependencies` / `devDependencies`)
12
+
13
+ | Пакет | Причина |
14
+ |-------|---------|
15
+ | `bunyan` | використовуй стандартні логери |
16
+ | `@nitra/bunyan` | використовуй стандартні логери |
17
+
18
+ **2. Заборонений рантайм у `scripts`** (лише backend-пакети без `vite`)
19
+
20
+ - Патерн `\bnode(\s|$)` — заміни `node` на `bun`
21
+ - Патерн `\benv\s+\$\(cat\s+[^)]+\)\s+bun\b` — заміни `env $(cat A B) bun` на `bun --env-file=A --env-file=B`
22
+
23
+ **✓ Правильно**
24
+
25
+ ```json
26
+ { "scripts": { "start": "bun src/index.js" } }
27
+ { "scripts": { "start": "bun --env-file=.env src/index.js" } }
28
+ ```
29
+
30
+ **✗ Неправильно**
31
+
32
+ ```json
33
+ { "scripts": { "start": "node src/index.js" } }
34
+ { "scripts": { "start": "env $(cat .env .env.local) bun src/index.js" } }
35
+ { "dependencies": { "bunyan": "^1.0.0" } }
36
+ ```
37
+
38
+ Пакети з `vite` у `devDependencies` — frontend, поза областю js-run, перевірка scripts не застосовується.
39
+
40
+ ## Використання @nitra/pino для логування
41
+
42
+ Проект використовує @nitra/pino для логування. Якщо в проекті присутній @nitra/bunyan, то він повинен бути замінений на @nitra/pino — як у `package.json`, так і в коді: усі `import` / `require` / динамічні `import()` з `@nitra/bunyan` (і застарілого `bunyan`) треба замінити на `@nitra/pino` і за потреби адаптувати виклики під його API.
43
+
44
+ Заборона `bunyan` / `@nitra/bunyan` у `dependencies` / `devDependencies` — rego-gate вище (канон: [package.json.deny.json](./template/package.json.deny.json)). Заборонені імпорти в коді (AST-сканер): `../lib/bunyan-imports.mjs` (перевірка запускається з `../runtime/main.mjs`).
@@ -0,0 +1,37 @@
1
+ # Перевірка `package.json` (js-run.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "deny": ... } }
4
+ # Структура --data сформована з template/package.json.deny.json.
5
+ # AST-скан коду (`bunyan`/`process.env`/`#conn/*`) — у JS.
6
+ package js_run.package_json
7
+
8
+ import rego.v1
9
+
10
+ deny contains msg if {
11
+ some pkg, reason in data.template.deny.dependencies
12
+ pkg in object.keys(object.get(input, "dependencies", {}))
13
+ msg := sprintf("dependencies.%s — %s", [pkg, reason])
14
+ }
15
+
16
+ deny contains msg if {
17
+ some pkg, reason in data.template.deny.devDependencies
18
+ pkg in object.keys(object.get(input, "devDependencies", {}))
19
+ msg := sprintf("devDependencies.%s — %s", [pkg, reason])
20
+ }
21
+
22
+ # ── deny: `node` як рантайм у `scripts` (backend-пакети без vite) ─────────
23
+
24
+ deny contains msg if {
25
+ js_run_backend_package
26
+ is_object(input.scripts)
27
+ some script_name, script_value in input.scripts
28
+ is_string(script_value)
29
+ some rule in object.get(data.template.deny, "scriptsForbidden", [])
30
+ regex.match(rule.pattern, script_value)
31
+ msg := sprintf("package.json: scripts.%s — %s", [script_name, rule.message])
32
+ }
33
+
34
+ # Frontend-пакети (`vite` у devDependencies) — поза js-run (див. js-run.mdc).
35
+ js_run_backend_package if {
36
+ not "vite" in object.keys(object.get(input, "devDependencies", {}))
37
+ }