@7n/rules-lang-js 0.3.1 → 0.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 (258) hide show
  1. package/CHANGELOG.md +6 -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 +32 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +68 -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,214 @@
1
+ /**
2
+ * Перевірки для файлів-підключень у каталозі `#conn` (js-run.mdc → «Нейминг файлів у `src/conn/`»
3
+ * та «Експорти у файлах `src/conn/`»).
4
+ *
5
+ * Канонічна назва файла:
6
+ * - GraphQL: `ql-<id>.{js|mjs|cjs|ts|mts|cts}` (id — kebab-case ідентифікатор endpoint);
7
+ * - PostgreSQL: `pg-{read|write}.{ext}` або `pg-{read|write}-<id>.{ext}` (id — для multi-БД);
8
+ * - MySQL: `mysql-{read|write}.{ext}` або `mysql-{read|write}-<id>.{ext}`;
9
+ * - MSSQL: `mssql-{read|write}.{ext}` або `mssql-{read|write}-<id>.{ext}`.
10
+ *
11
+ * Канонічний експорт — іменований, без `export default`. Імʼя константи має дорівнювати
12
+ * camelCase від basename файла (`pg-write-contract` → `pgWriteContract`).
13
+ *
14
+ * Парсимо через oxc-parser; коли файл не парситься — повертаємо порожні результати, щоб
15
+ * не змішувати помилки синтаксису з порушеннями цього правила.
16
+ */
17
+ import { parseProgramOrNull } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
18
+
19
+ const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
20
+
21
+ /**
22
+ * Канонічний шаблон імені GraphQL-файла: `ql-<id>.<ext>`.
23
+ * `<id>` — kebab без leading/trailing-`-`, починається/закінчується на `[a-z0-9]`.
24
+ */
25
+ const CONN_FILENAME_QL_RE = /^ql-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[cm]?[jt]sx?$/u
26
+ /**
27
+ * Канонічний шаблон імені файла БД-підключення: `(pg|mysql|mssql)-(read|write)(-<id>)?.<ext>`.
28
+ * `<id>` — за тими ж правилами, що й для `ql-`. Розділили з GraphQL-формою, щоб
29
+ * не множити комплексність regex (sonarjs/regex-complexity).
30
+ */
31
+ const CONN_FILENAME_DB_RE = /^(?:pg|mysql|mssql)-(?:read|write)(?:-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)?\.[cm]?[jt]sx?$/u
32
+
33
+ /**
34
+ * Чи це файл, який сканується правилом «conn-file» (JS/TS-сімʼя, без `.d.ts`).
35
+ * @param {string} relativePathPosix відносний posix-шлях
36
+ * @returns {boolean} true, якщо потрібно перевіряти
37
+ */
38
+ export function isConnFileRulesSourceFile(relativePathPosix) {
39
+ return SOURCE_FILE_RE.test(relativePathPosix) && !relativePathPosix.endsWith('.d.ts')
40
+ }
41
+
42
+ /**
43
+ * Витягує basename файла без розширення.
44
+ * @param {string} relativePathPosix відносний шлях у posix-форматі
45
+ * @returns {string} basename без розширення (наприклад, `pg-write-contract`)
46
+ */
47
+ function basenameNoExt(relativePathPosix) {
48
+ const last = relativePathPosix.lastIndexOf('/')
49
+ const base = last === -1 ? relativePathPosix : relativePathPosix.slice(last + 1)
50
+ const dot = base.lastIndexOf('.')
51
+ return dot > 0 ? base.slice(0, dot) : base
52
+ }
53
+
54
+ /**
55
+ * Перетворює kebab-case ідентифікатор у camelCase.
56
+ * @param {string} kebab kebab-case рядок (`pg-write-contract`)
57
+ * @returns {string} camelCase (`pgWriteContract`)
58
+ */
59
+ export function kebabToCamel(kebab) {
60
+ return kebab.replaceAll(/-([a-z0-9])/gu, (_m, c) => c.toUpperCase())
61
+ }
62
+
63
+ /**
64
+ * Чи відповідає назва файла канонічному шаблону для каталогу conn.
65
+ * @param {string} relativePathPosix відносний posix-шлях файла
66
+ * @returns {boolean} true, якщо basename + ext збігається зі схемою
67
+ */
68
+ export function isConnFileNameValid(relativePathPosix) {
69
+ const last = relativePathPosix.lastIndexOf('/')
70
+ const base = last === -1 ? relativePathPosix : relativePathPosix.slice(last + 1)
71
+ return CONN_FILENAME_QL_RE.test(base) || CONN_FILENAME_DB_RE.test(base)
72
+ }
73
+
74
+ /**
75
+ * Витягує імена з `export const/let/var X = …` (включно з кількома declarators у одному `export const a, b`).
76
+ * @param {Record<string, unknown>} decl AST `VariableDeclaration`
77
+ * @returns {string[]} імена змінних
78
+ */
79
+ function namesFromVariableDeclaration(decl) {
80
+ if (!Array.isArray(decl.declarations)) return []
81
+ /** @type {string[]} */
82
+ const out = []
83
+ for (const d of decl.declarations) {
84
+ const id = /** @type {Record<string, unknown> | null} */ (d?.id ?? null)
85
+ if (id && id.type === 'Identifier' && typeof id.name === 'string') out.push(id.name)
86
+ }
87
+ return out
88
+ }
89
+
90
+ /**
91
+ * Витягує імʼя з `export function X` / `export class X`.
92
+ * @param {Record<string, unknown>} decl AST `FunctionDeclaration` або `ClassDeclaration`
93
+ * @returns {string | null} імʼя або `null`, якщо id-вузол анонімний
94
+ */
95
+ function nameFromFnOrClassDeclaration(decl) {
96
+ if (decl.type !== 'FunctionDeclaration' && decl.type !== 'ClassDeclaration') return null
97
+ const id = /** @type {Record<string, unknown> | null} */ (decl.id ?? null)
98
+ if (!id || typeof id !== 'object') return null
99
+ return typeof id.name === 'string' ? id.name : null
100
+ }
101
+
102
+ /**
103
+ * Витягує експортоване імʼя з одного `ExportSpecifier` (`export { X }` / `export { X as Y }`).
104
+ * @param {Record<string, unknown> | null | undefined} specifier AST `ExportSpecifier`
105
+ * @returns {string | null} імʼя або `null`
106
+ */
107
+ function nameFromExportSpecifier(specifier) {
108
+ const exported = /** @type {Record<string, unknown> | null} */ (specifier?.exported ?? null)
109
+ if (!exported) return null
110
+ if (exported.type === 'Identifier' && typeof exported.name === 'string') return exported.name
111
+ if (typeof exported.value === 'string') return exported.value
112
+ return null
113
+ }
114
+
115
+ /**
116
+ * Імена з одного `ExportNamedDeclaration` — або з вкладеного `declaration`, або зі списку `specifiers`.
117
+ * @param {Record<string, unknown>} rec AST `ExportNamedDeclaration`
118
+ * @returns {string[]} імена цього експортного вузла
119
+ */
120
+ function namesFromNamedExport(rec) {
121
+ const decl = /** @type {Record<string, unknown> | null} */ (rec.declaration ?? null)
122
+ if (decl) {
123
+ if (decl.type === 'VariableDeclaration') return namesFromVariableDeclaration(decl)
124
+ const fnOrClass = nameFromFnOrClassDeclaration(decl)
125
+ return fnOrClass ? [fnOrClass] : []
126
+ }
127
+ if (!Array.isArray(rec.specifiers)) return []
128
+ /** @type {string[]} */
129
+ const out = []
130
+ for (const s of rec.specifiers) {
131
+ const name = nameFromExportSpecifier(/** @type {Record<string, unknown> | null} */ (s ?? null))
132
+ if (name) out.push(name)
133
+ }
134
+ return out
135
+ }
136
+
137
+ /**
138
+ * Збирає всі імена named-експортів у програмі.
139
+ *
140
+ * Покриває: `export const/let/var X`, `export function X`, `export class X`,
141
+ * `export { X }`, `export { X as Y }` (повертає `Y`). `export *` ігнорується
142
+ * (немає конкретного імені для звірки), `export default` обробляється окремо.
143
+ * @param {unknown} program AST root
144
+ * @returns {string[]} список експортованих імен
145
+ */
146
+ function collectNamedExportNames(program) {
147
+ /** @type {string[]} */
148
+ const out = []
149
+ if (!program || typeof program !== 'object') return out
150
+ const body = /** @type {Record<string, unknown>} */ (program).body
151
+ if (!Array.isArray(body)) return out
152
+ for (const node of body) {
153
+ if (!node || typeof node !== 'object') continue
154
+ const rec = /** @type {Record<string, unknown>} */ (node)
155
+ if (rec.type !== 'ExportNamedDeclaration') continue
156
+ out.push(...namesFromNamedExport(rec))
157
+ }
158
+ return out
159
+ }
160
+
161
+ /**
162
+ * Чи є в програмі `export default ...`.
163
+ * @param {unknown} program AST root
164
+ * @returns {boolean} true, якщо знайдено будь-який ExportDefaultDeclaration
165
+ */
166
+ function hasDefaultExport(program) {
167
+ if (!program || typeof program !== 'object') return false
168
+ const body = /** @type {Record<string, unknown>} */ (program).body
169
+ if (!Array.isArray(body)) return false
170
+ for (const node of body) {
171
+ if (
172
+ node &&
173
+ typeof node === 'object' &&
174
+ /** @type {Record<string, unknown>} */ (node).type === 'ExportDefaultDeclaration'
175
+ ) {
176
+ return true
177
+ }
178
+ }
179
+ return false
180
+ }
181
+
182
+ /**
183
+ * Знаходить порушення правил для одного файла з каталогу conn.
184
+ *
185
+ * Якщо AST не парситься — повертає порожній масив (синтаксис падає в інших перевірках,
186
+ * не дублюємо).
187
+ * @param {string} content вихідний код файла
188
+ * @param {string} relativePathPosix відносний posix-шлях файла (від кореня пакета)
189
+ * @returns {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }[]} список порушень
190
+ */
191
+ export function findConnFileRuleViolations(content, relativePathPosix) {
192
+ /** @type {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }[]} */
193
+ const out = []
194
+ if (!isConnFileNameValid(relativePathPosix)) {
195
+ out.push({ kind: 'name' })
196
+ // якщо назва нестандартна — далі звірку імені експорту не робимо (camelCase двозначний)
197
+ }
198
+
199
+ const program = parseProgramOrNull(content, relativePathPosix)
200
+ if (!program) return out
201
+
202
+ if (hasDefaultExport(program)) {
203
+ out.push({ kind: 'default-export' })
204
+ }
205
+
206
+ if (out.some(v => v.kind === 'name')) return out
207
+
208
+ const expected = kebabToCamel(basenameNoExt(relativePathPosix.slice(relativePathPosix.lastIndexOf('/') + 1)))
209
+ const names = collectNamedExportNames(program)
210
+ if (!names.includes(expected)) {
211
+ out.push({ kind: 'export-name', expectedName: expected, foundNames: names })
212
+ }
213
+ return out
214
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * AST-сканер для правила «Внутрішні аліаси» (js-run.mdc).
3
+ *
4
+ * Імпорти, які створюють підключення до БД / зовнішнього GraphQL, мають жити в окремому
5
+ * файлі (за замовчуванням — `src/conn/`), а решта коду повинна споживати їх через
6
+ * pkg-import `#conn/...`. Ловимо такі імпорти в файлах поза каталогом «conn»:
7
+ * - `import { SQL } from 'bun'` (named специфікатор `SQL`);
8
+ * - `import sql from 'mssql'` або будь-який `import ... from 'mssql'`;
9
+ * - `import { GraphQLClient } from '@nitra/graphql-request'` (named `GraphQLClient`).
10
+ *
11
+ * Каталог «conn» визначається з поля `package.json#imports['#conn/*']` (якщо є —
12
+ * відрізаємо `*` і нормалізуємо), інакше дефолт — `src/conn`. Ключ `imports` у
13
+ * package.json — нативний для Node.js, той самий, що й у документі правила.
14
+ *
15
+ * Семантика береться з **oxc-parser** (`module.staticImports`); regex по тілу файлу не
16
+ * використовується. Якщо файл не парситься — повертаємо порожній результат, спочатку
17
+ * треба полагодити синтаксис.
18
+ */
19
+ import { langFromPath, normalizeSnippet, offsetToLine } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
20
+ import { parseSync } from 'oxc-parser'
21
+
22
+ const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
23
+
24
+ /**
25
+ * Прибирає хвостові `/` зі шляху без використання regex (щоб не тригерити slow-regex попередження).
26
+ * @param {string} s рядок зі шляхом
27
+ * @returns {string} `s` без хвостових `/`
28
+ */
29
+ function stripTrailingSlashes(s) {
30
+ let end = s.length
31
+ while (end > 0 && s.codePointAt(end - 1) === 47) end--
32
+ return end === s.length ? s : s.slice(0, end)
33
+ }
34
+
35
+ /**
36
+ * Нормалізує шлях до posix без хвостових слешів.
37
+ * @param {string} p вхідний шлях (можливо з `./` або зворотними слешами)
38
+ * @returns {string} нормалізований posix-шлях без хвостового `/`
39
+ */
40
+ function toPosixDir(p) {
41
+ let s = String(p).replaceAll('\\', '/').trim()
42
+ if (s.startsWith('./')) s = s.slice(2)
43
+ return stripTrailingSlashes(s)
44
+ }
45
+
46
+ /**
47
+ * Визначає каталог «conn» за `package.json#imports['#conn/*']`. Дефолт — `src/conn`.
48
+ * @param {unknown} pkgJson розпарсений package.json (або null)
49
+ * @returns {string} відносний posix-шлях до каталогу conn (без хвостового `/`)
50
+ */
51
+ export function resolveConnDirFromPackageJson(pkgJson) {
52
+ const fallback = 'src/conn'
53
+ if (!pkgJson || typeof pkgJson !== 'object') return fallback
54
+ const imports = /** @type {Record<string, unknown>} */ (pkgJson).imports
55
+ if (!imports || typeof imports !== 'object') return fallback
56
+ const target = /** @type {Record<string, unknown>} */ (imports)['#conn/*']
57
+ /** @type {string | null} */
58
+ let raw = null
59
+ if (typeof target === 'string') raw = target
60
+ else if (target && typeof target === 'object') {
61
+ // умовний експорт: { default: '...', import: '...' }
62
+ const obj = /** @type {Record<string, unknown>} */ (target)
63
+ if (typeof obj.default === 'string') raw = obj.default
64
+ else if (typeof obj.import === 'string') raw = obj.import
65
+ }
66
+ if (!raw) return fallback
67
+ // Прибираємо хвіст `*`, потім слеші
68
+ let s = toPosixDir(raw)
69
+ if (s.endsWith('/*')) s = s.slice(0, -2)
70
+ return stripTrailingSlashes(s) || fallback
71
+ }
72
+
73
+ /**
74
+ * Чи перебуває файл у каталозі conn (точно або вкладено).
75
+ * @param {string} relPosix відносний posix-шлях до файлу
76
+ * @param {string} connDir posix-шлях каталогу conn (без хвостового `/`)
77
+ * @returns {boolean} true, якщо файл у каталозі conn
78
+ */
79
+ export function isInsideConnDir(relPosix, connDir) {
80
+ if (!connDir) return false
81
+ return relPosix === connDir || relPosix.startsWith(`${connDir}/`)
82
+ }
83
+
84
+ /**
85
+ * Чи це порушення правила «Внутрішні аліаси» — імпорт зі стороннього модуля, що створює
86
+ * підключення (`bun` зі специфікатором `SQL`, будь-який імпорт з `mssql`, або
87
+ * `@nitra/graphql-request` зі специфікатором `GraphQLClient`).
88
+ * @param {Record<string, unknown>} staticImport елемент `module.staticImports` з oxc-parser
89
+ * @returns {{ module: string, specifier: string } | null} опис порушення або null
90
+ */
91
+ function classifyConnImport(staticImport) {
92
+ const mod = staticImport.moduleRequest?.value
93
+ if (typeof mod !== 'string') return null
94
+ const entries = Array.isArray(staticImport.entries) ? staticImport.entries : []
95
+
96
+ if (mod === 'bun') {
97
+ for (const e of entries) {
98
+ const name = e?.importName?.name
99
+ if (name === 'SQL') return { module: mod, specifier: 'SQL' }
100
+ }
101
+ return null
102
+ }
103
+ if (mod === 'mssql') {
104
+ return { module: mod, specifier: '*' }
105
+ }
106
+ if (mod === '@nitra/graphql-request') {
107
+ for (const e of entries) {
108
+ const name = e?.importName?.name
109
+ if (name === 'GraphQLClient') return { module: mod, specifier: 'GraphQLClient' }
110
+ }
111
+ return null
112
+ }
113
+ return null
114
+ }
115
+
116
+ /**
117
+ * Знаходить імпорти-«фабрики підключень» у тексті файлу.
118
+ * @param {string} content вихідний код
119
+ * @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/index.ts`)
120
+ * @returns {{ line: number, snippet: string, module: string, specifier: string }[]} список порушень
121
+ */
122
+ export function findConnFactoryImportsInText(content, virtualPath = 'scan.ts') {
123
+ const lang = langFromPath(virtualPath || 'scan.ts')
124
+ let result
125
+ try {
126
+ result = parseSync(virtualPath || 'scan.ts', content, { lang, sourceType: 'module' })
127
+ } catch {
128
+ return []
129
+ }
130
+ if (result.errors?.length) return []
131
+
132
+ /** @type {{ line: number, snippet: string, module: string, specifier: string }[]} */
133
+ const out = []
134
+ for (const imp of result.module?.staticImports ?? []) {
135
+ const hit = classifyConnImport(imp)
136
+ if (!hit) continue
137
+ out.push({
138
+ line: offsetToLine(content, imp.start),
139
+ snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
140
+ module: hit.module,
141
+ specifier: hit.specifier
142
+ })
143
+ }
144
+ return out
145
+ }
146
+
147
+ /**
148
+ * Чи сканувати цей файл за розширенням (JS/TS-сім'я, без `.d.ts`).
149
+ * @param {string} relativePathPosix відносний шлях (posix)
150
+ * @returns {boolean} true, якщо розширення підходить для AST-скану
151
+ */
152
+ export function isConnImportsScanSourceFile(relativePathPosix) {
153
+ return SOURCE_FILE_RE.test(relativePathPosix) && !relativePathPosix.endsWith('.d.ts')
154
+ }
@@ -0,0 +1,121 @@
1
+ ---
2
+ type: JS Module
3
+ title: bunyan-imports.mjs
4
+ resource: plugins/lang-js/rules/js-run/lib/bunyan-imports.mjs
5
+ docgen:
6
+ crc: 2494f203
7
+ ---
8
+
9
+ Модуль `bunyan-imports.mjs` — це AST-сканер, який знаходить у вихідних файлах JavaScript / TypeScript заборонені імпорти й виклики модулів `@nitra/bunyan` та застарілого `bunyan`. Згідно з правилом `js-run.mdc` уся логіка структурованого логування у проєкті переведена на `@nitra/pino`, тому будь-яке згадування `bunyan`/`@nitra/bunyan` має бути замінене на `@nitra/pino`.
10
+
11
+ Ключові властивості:
12
+
13
+ - Семантика береться з **oxc-parser** (`module.staticImports`) — без regex по тілу файлу; це робить сканер стійким до коментарів, рядкових літералів та форматувань.
14
+ - На додачу до статичних `import`-ів сканер обходить AST програми і ловить:
15
+ - `require('@nitra/bunyan')` / `require('bunyan')` — для CommonJS;
16
+ - динамічний `import('@nitra/bunyan')` / `import('bunyan')` — для випадків асинхронного підключення в межах одного файлу.
17
+ - Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках парсера повертається **порожній** результат — спершу слід полагодити синтаксис, потім перезапустити перевірку.
18
+ - Декларації типів (`*.d.ts`) виключені з обходу через окремий хелпер.
19
+
20
+ Файл є частиною бібліотеки правила `js-run` й використовується чек-скриптами цього правила для сигналізації про порушення.
21
+
22
+ ## Експорти / API
23
+
24
+ Модуль експортує три іменовані функції:
25
+
26
+ | Експорт | Призначення |
27
+ | ------------------------------------------------ | ----------------------------------------------------------- |
28
+ | `findBunyanImportsInText(content, virtualPath?)` | Основний AST-сканер тексту файлу — повертає масив порушень. |
29
+ | `isBunyanScanSourceFile(relativePath)` | Фільтр за розширенням файлу (JS/TS-сім'я). |
30
+ | `shouldSkipFileForBunyanScan(relativePosix)` | Виключення декларацій типів (`*.d.ts`). |
31
+
32
+ Внутрішні (не експортовані) константи:
33
+
34
+ - `SOURCE_FILE_RE` — `/\.([cm]?[jt]sx?)$/u` — регулярний вираз для розширень `.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`.
35
+ - `FORBIDDEN_MODULES` — `Set` з двох імен модулів: `'@nitra/bunyan'` і `'bunyan'`.
36
+
37
+ ## Функції
38
+
39
+ ### `findBunyanImportsInText(content, virtualPath = 'scan.ts')`
40
+
41
+ Основний сканер. Парсить переданий текст як модуль JS/TS і збирає всі місця, де згадуються заборонені модулі через статичний `import`, `require(...)` або динамічний `import(...)`.
42
+
43
+ - Сигнатура: `findBunyanImportsInText(content: string, virtualPath?: string) => { line: number, snippet: string, module: string }[]`
44
+ - Параметри:
45
+ - `content` (`string`) — повний вихідний код, який треба перевірити.
46
+ - `virtualPath` (`string`, опційно, за замовчуванням `'scan.ts'`) — віртуальний шлях, який передається в парсер; використовується тільки для вибору `lang` (TS/JS/JSX/TSX) через `langFromPath(...)`. Реальний файл на диску не читається.
47
+ - Повертає: масив об'єктів-порушень. Кожен елемент:
48
+ - `line` — номер рядка (1-індекс) місця початку конструкції (`offsetToLine`).
49
+ - `snippet` — нормалізований текстовий зріз `content.slice(start, end)` через `normalizeSnippet` (стискання пробілів/обрізання).
50
+ - `module` — рядок самого імпортованого імені (`'@nitra/bunyan'` або `'bunyan'`).
51
+ - Порядок результату: спершу всі статичні `import`-и в порядку їх появи в `module.staticImports`, далі — `require`/`import(...)` у порядку обходу AST через `walkAstWithAncestors`.
52
+ - Side effects: відсутні. Функція суто функціональна: жодних I/O, мутацій вхідних параметрів чи логів. Виняткова поведінка `parseSync` поглинається `try/catch`, помилки парсера в `result.errors` трактуються як «нічого не знайдено» — повертається `[]`.
53
+ - Граничні випадки:
54
+ - `virtualPath` falsy (порожній рядок, `undefined`) — використовується запасне значення `'scan.ts'`.
55
+ - Якщо `parseSync` кинув виняток або у `result.errors` є записи — повертається `[]`.
56
+ - Якщо `result.module` відсутній (наприклад, скрипт CJS), цикл по `module.staticImports` пропускається через `?? []`, AST-обхід усе одно виконується.
57
+ - Для `require`/динамічного `import` модулі з не-літеральним аргументом (`require(name)`) хелпери з `ast-scan-utils` повернуть `null` і вузол буде проігноровано.
58
+
59
+ ### `isBunyanScanSourceFile(relativePath)`
60
+
61
+ Швидкий фільтр за розширенням — чи має сенс взагалі парсити цей файл.
62
+
63
+ - Сигнатура: `isBunyanScanSourceFile(relativePath: string) => boolean`
64
+ - Параметри:
65
+ - `relativePath` (`string`) — відносний шлях до файлу (формат розділювача не важливий, перевіряється тільки суфікс).
66
+ - Повертає: `true`, якщо розширення входить у JS/TS-сім'ю (`.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`); інакше `false`.
67
+ - Side effects: відсутні; чиста перевірка регулярним виразом `SOURCE_FILE_RE`.
68
+
69
+ ### `shouldSkipFileForBunyanScan(relativePosix)`
70
+
71
+ Виключення для декларацій типів TypeScript.
72
+
73
+ - Сигнатура: `shouldSkipFileForBunyanScan(relativePosix: string) => boolean`
74
+ - Параметри:
75
+ - `relativePosix` (`string`) — шлях у posix-форматі (з `/`).
76
+ - Повертає: `true`, якщо шлях завершується на `.d.ts` — такий файл містить лише типи й не повинен сканеритися; інакше `false`.
77
+ - Side effects: відсутні.
78
+ - Примітка: цей хелпер логічно йде паралельно до `isBunyanScanSourceFile`. Файл `*.d.ts` теж задовольнить `SOURCE_FILE_RE`, тож обхідник пакета має застосовувати **обидва** хелпери: спочатку відсіяти за розширенням, потім додатково пропустити `.d.ts`.
79
+
80
+ ## Залежності
81
+
82
+ Зовнішні (`node_modules`):
83
+
84
+ - `oxc-parser` — парсер JS/TS, експортує `parseSync`. Сканер користується вузлами `module.staticImports` (метадані статичних `import`-декларацій) та повним AST програми (`result.program`).
85
+
86
+ Внутрішні (відносний імпорт з `../../../scripts/utils/ast-scan-utils.mjs`):
87
+
88
+ - `dynamicImportModule(node)` — повертає рядок-аргумент динамічного `import('...')` або `null`.
89
+ - `langFromPath(path)` — визначає `lang` (`'ts'`, `'tsx'`, `'js'`, `'jsx'`, …) за розширенням шляху для `parseSync`.
90
+ - `normalizeSnippet(text)` — нормалізує сирий шматок коду перед збереженням у результат (типово стискає пробіли / обрізає).
91
+ - `offsetToLine(content, offset)` — перетворює числовий offset у тексті на 1-індексований номер рядка.
92
+ - `requireCallModule(node)` — повертає рядок-аргумент виклику `require('...')` або `null`.
93
+ - `walkAstWithAncestors(program, ancestors, visitor)` — обхід AST з accumulator-предків (тут передається порожній масив; колбек дивиться лише на поточний `node`).
94
+
95
+ Очікувані виклики (хто використовує цей модуль): чек-скрипти правила `js-run` (наприклад, `npm/rules/js-run/check-*.mjs`), які отримують вміст файлів пакета й передають їх у `findBunyanImportsInText`, після чого формують повідомлення про порушення з `line` / `snippet` / `module`. Обхідник файлів пакета використовує `isBunyanScanSourceFile` для фільтра кандидатів і `shouldSkipFileForBunyanScan` для виключень.
96
+
97
+ ## Потік виконання / Використання
98
+
99
+ Типовий сценарій інтеграції в чек-правила:
100
+
101
+ 1. Обхідник перебирає всі файли пакета та конвертує шляхи у posix-формат.
102
+ 2. Для кожного `relativePosix` викликається `isBunyanScanSourceFile(relativePosix)`. Якщо `false` — файл пропускається.
103
+ 3. Якщо `shouldSkipFileForBunyanScan(relativePosix)` повертає `true` (тобто `*.d.ts`) — файл також пропускається.
104
+ 4. Інакше файл читається з диска (зовнішнім кодом), а його вміст подається у `findBunyanImportsInText(content, relativePosix)`.
105
+ 5. Усередині `findBunyanImportsInText`:
106
+ 1. Обчислюється `lang` через `langFromPath(virtualPath)`.
107
+ 2. Викликається `parseSync(virtualPath, content, { lang, sourceType: 'module' })` всередині `try/catch`.
108
+ 3. Якщо парсер кинув виняток або в `result.errors` є записи — повертається `[]`.
109
+ 4. Інакше перебираються `result.module?.staticImports ?? []`; для кожного `import`, де `moduleRequest.value` входить у `FORBIDDEN_MODULES`, до результату додається запис `{ line, snippet, module }`.
110
+ 5. Далі `walkAstWithAncestors(result.program, [], visitor)` проходить по AST. У відвідувача:
111
+ - `requireCallModule(node)` повертає не-`null` для `require('...')` — якщо модуль заборонений, додаємо запис; інакше пропускаємо.
112
+ - `dynamicImportModule(node)` — те саме для динамічного `import('...')`.
113
+ 6. Викликач отримує плаский масив порушень і формує звіт (наприклад, друкує `path:line` та `snippet`).
114
+
115
+ Гарантії та особливості:
116
+
117
+ - Чисто синхронний прохід — жодного I/O всередині модуля.
118
+ - Якщо файл містить синтаксичні помилки, результат `[]` означає **не «все добре»**, а «парсер не зрозумів файл» — це усвідомлене дизайн-рішення: спершу виправити синтаксис, потім перезапустити перевірку.
119
+ - Регулярка `SOURCE_FILE_RE` не покриває нестандартні розширення (наприклад, `.vue`, `.svelte`). Якщо для них потрібен власний пайплайн (з вилученням `<script>` блоку), його треба будувати окремо.
120
+ - Між списком `module.staticImports` і обходом AST вузли `import` не дублюються: статичні `import`-декларації обходяться лише через `module.staticImports`, а у візиторі перевіряються тільки `require` та динамічний `import(...)`.
121
+ - Перелік заборонених модулів суворо обмежений `FORBIDDEN_MODULES` (рівність рядків). Підшляхи на кшталт `@nitra/bunyan/foo` не вловлюються — це навмисна вузька семантика правила.