@7n/rules 1.27.0 → 1.28.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 (268) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -5
  3. package/schemas/v8r-catalog.json +10 -1
  4. package/scripts/auto-rules.mjs +2 -3
  5. package/scripts/docs/auto-rules.md +1 -1
  6. package/scripts/lib/docs/glob-to-regex.md +31 -0
  7. package/scripts/lib/docs/index.md +2 -0
  8. package/scripts/lib/docs/js-source-signals.md +32 -0
  9. package/scripts/lib/glob-to-regex.mjs +79 -0
  10. package/scripts/lib/js-source-signals.mjs +50 -0
  11. package/scripts/utils/ast-scan-utils.mjs +2 -2
  12. package/scripts/utils/docs/ast-scan-utils.md +1 -1
  13. package/rules/bun/bunfig/bunfig.mdc +0 -17
  14. package/rules/bun/bunfig/bunfig.rego +0 -29
  15. package/rules/bun/bunfig/concern.json +0 -9
  16. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +0 -2
  17. package/rules/bun/docs/index.md +0 -11
  18. package/rules/bun/layout/concern.json +0 -16
  19. package/rules/bun/layout/docs/fix-layout.md +0 -29
  20. package/rules/bun/layout/docs/main.md +0 -34
  21. package/rules/bun/layout/fix-layout.mjs +0 -63
  22. package/rules/bun/layout/layout.mdc +0 -60
  23. package/rules/bun/layout/main.mjs +0 -53
  24. package/rules/bun/licensee/concern.json +0 -7
  25. package/rules/bun/licensee/docs/fix-licensee.md +0 -27
  26. package/rules/bun/licensee/docs/index.md +0 -12
  27. package/rules/bun/licensee/docs/main.md +0 -30
  28. package/rules/bun/licensee/fix-licensee.mjs +0 -31
  29. package/rules/bun/licensee/main.mjs +0 -68
  30. package/rules/bun/lint-surface/concern.json +0 -3
  31. package/rules/bun/lint-surface/lint-surface.mdc +0 -13
  32. package/rules/bun/main.json +0 -1
  33. package/rules/bun/main.mdc +0 -11
  34. package/rules/bun/package_json/concern.json +0 -9
  35. package/rules/bun/package_json/docs/fix-package_json.md +0 -28
  36. package/rules/bun/package_json/docs/index.md +0 -9
  37. package/rules/bun/package_json/fix-package_json.mjs +0 -311
  38. package/rules/bun/package_json/package_json.mdc +0 -14
  39. package/rules/bun/package_json/package_json.rego +0 -64
  40. package/rules/bun/package_json/template/package.json.deny.json +0 -4
  41. package/rules/js/check/check.mdc +0 -26
  42. package/rules/js/check/concern.json +0 -19
  43. package/rules/js/check/docs/eslint-config.md +0 -56
  44. package/rules/js/check/docs/fix-check.md +0 -48
  45. package/rules/js/check/docs/index.md +0 -13
  46. package/rules/js/check/docs/main.md +0 -48
  47. package/rules/js/check/eslint-config.mjs +0 -262
  48. package/rules/js/check/fix-check.mjs +0 -82
  49. package/rules/js/check/main.mjs +0 -310
  50. package/rules/js/dep-policy/concern.json +0 -4
  51. package/rules/js/dep-policy/dep-policy.mdc +0 -36
  52. package/rules/js/dep-policy/docs/main.md +0 -35
  53. package/rules/js/dep-policy/main.mjs +0 -99
  54. package/rules/js/docs/index.md +0 -11
  55. package/rules/js/eslint/concern.json +0 -8
  56. package/rules/js/eslint/docs/fix-eslint.md +0 -50
  57. package/rules/js/eslint/docs/fix-worker.md +0 -31
  58. package/rules/js/eslint/docs/index.md +0 -11
  59. package/rules/js/eslint/docs/main.md +0 -35
  60. package/rules/js/eslint/fix-eslint.mjs +0 -160
  61. package/rules/js/eslint/fix-worker.mjs +0 -139
  62. package/rules/js/eslint/main.mjs +0 -115
  63. package/rules/js/file-extensions/concern.json +0 -3
  64. package/rules/js/file-extensions/file-extensions.mdc +0 -12
  65. package/rules/js/jscpd_config/concern.json +0 -11
  66. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +0 -26
  67. package/rules/js/jscpd_config/docs/index.md +0 -9
  68. package/rules/js/jscpd_config/fix-jscpd_config.mjs +0 -3
  69. package/rules/js/jscpd_config/jscpd_config.mdc +0 -42
  70. package/rules/js/jscpd_config/jscpd_config.rego +0 -44
  71. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +0 -7
  72. package/rules/js/jscpd_duplicates/concern.json +0 -7
  73. package/rules/js/jscpd_duplicates/docs/main.md +0 -29
  74. package/rules/js/jscpd_duplicates/main.mjs +0 -69
  75. package/rules/js/knip/concern.json +0 -7
  76. package/rules/js/knip/docs/main.md +0 -32
  77. package/rules/js/knip/knip.mdc +0 -15
  78. package/rules/js/knip/main.mjs +0 -68
  79. package/rules/js/lint-findings/concern.json +0 -3
  80. package/rules/js/lint-findings/docs/main.md +0 -41
  81. package/rules/js/lint-findings/main.mjs +0 -125
  82. package/rules/js/main.json +0 -1
  83. package/rules/js/main.mdc +0 -18
  84. package/rules/js/package_json/concern.json +0 -9
  85. package/rules/js/package_json/docs/fix-package_json.md +0 -26
  86. package/rules/js/package_json/docs/index.md +0 -9
  87. package/rules/js/package_json/fix-package_json.mjs +0 -3
  88. package/rules/js/package_json/package_json.mdc +0 -15
  89. package/rules/js/package_json/package_json.rego +0 -142
  90. package/rules/js/package_json/template/package.json.snippet.json +0 -6
  91. package/rules/js/tooling/concern.json +0 -3
  92. package/rules/js/tooling/data/tooling/knip-canonical.json +0 -30
  93. package/rules/js/tooling/data/tooling/oxlint-canonical.json +0 -400
  94. package/rules/js/tooling/docs/main.md +0 -53
  95. package/rules/js/tooling/main.mjs +0 -183
  96. package/rules/js/utils_imports/concern.json +0 -4
  97. package/rules/js/utils_imports/docs/main.md +0 -50
  98. package/rules/js/utils_imports/main.mjs +0 -185
  99. package/rules/js/utils_imports/utils_imports.mdc +0 -15
  100. package/rules/js/vscode_extensions/concern.json +0 -11
  101. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
  102. package/rules/js/vscode_extensions/docs/index.md +0 -11
  103. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +0 -1
  104. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +0 -6
  105. package/rules/js/vscode_extensions/vscode_extensions.mdc +0 -11
  106. package/rules/js/vscode_extensions/vscode_extensions.rego +0 -12
  107. package/rules/js-bun-db/connection/concern.json +0 -3
  108. package/rules/js-bun-db/connection/connection.mdc +0 -42
  109. package/rules/js-bun-db/docs/index.md +0 -11
  110. package/rules/js-bun-db/lib/bun-sql-scan.mjs +0 -1055
  111. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +0 -63
  112. package/rules/js-bun-db/lib/docs/index.md +0 -11
  113. package/rules/js-bun-db/main.json +0 -1
  114. package/rules/js-bun-db/main.mdc +0 -8
  115. package/rules/js-bun-db/package_json/concern.json +0 -9
  116. package/rules/js-bun-db/package_json/package_json.mdc +0 -31
  117. package/rules/js-bun-db/package_json/package_json.rego +0 -15
  118. package/rules/js-bun-db/package_json/template/package.json.deny.json +0 -6
  119. package/rules/js-bun-db/pg_format_identifiers/concern.json +0 -3
  120. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +0 -104
  121. package/rules/js-bun-db/safety/concern.json +0 -4
  122. package/rules/js-bun-db/safety/docs/main.md +0 -34
  123. package/rules/js-bun-db/safety/main.mjs +0 -430
  124. package/rules/js-bun-db/safety/safety.mdc +0 -458
  125. package/rules/js-bun-redis/docs/index.md +0 -11
  126. package/rules/js-bun-redis/imports/concern.json +0 -4
  127. package/rules/js-bun-redis/imports/docs/main.md +0 -36
  128. package/rules/js-bun-redis/imports/imports.mdc +0 -47
  129. package/rules/js-bun-redis/imports/main.mjs +0 -88
  130. package/rules/js-bun-redis/lib/docs/index.md +0 -11
  131. package/rules/js-bun-redis/lib/docs/redis-imports.md +0 -227
  132. package/rules/js-bun-redis/lib/redis-imports.mjs +0 -130
  133. package/rules/js-bun-redis/main.json +0 -1
  134. package/rules/js-bun-redis/main.mdc +0 -8
  135. package/rules/js-bun-redis/package_json/concern.json +0 -9
  136. package/rules/js-bun-redis/package_json/package_json.mdc +0 -11
  137. package/rules/js-bun-redis/package_json/package_json.rego +0 -15
  138. package/rules/js-bun-redis/package_json/template/package.json.deny.json +0 -12
  139. package/rules/js-mssql/deps/concern.json +0 -4
  140. package/rules/js-mssql/deps/docs/main.md +0 -33
  141. package/rules/js-mssql/deps/main.mjs +0 -297
  142. package/rules/js-mssql/docs/index.md +0 -11
  143. package/rules/js-mssql/lib/docs/index.md +0 -11
  144. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +0 -380
  145. package/rules/js-mssql/lib/mssql-pool-scan.mjs +0 -610
  146. package/rules/js-mssql/main.json +0 -1
  147. package/rules/js-mssql/main.mdc +0 -144
  148. package/rules/js-mssql/mssql-tvp/concern.json +0 -3
  149. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +0 -77
  150. package/rules/js-mssql/package_json/concern.json +0 -9
  151. package/rules/js-mssql/package_json/package_json.mdc +0 -9
  152. package/rules/js-mssql/package_json/package_json.rego +0 -57
  153. package/rules/js-run/configmap/concern.json +0 -9
  154. package/rules/js-run/configmap/configmap.mdc +0 -37
  155. package/rules/js-run/configmap/configmap.rego +0 -21
  156. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +0 -4
  157. package/rules/js-run/docs/index.md +0 -11
  158. package/rules/js-run/jsconfig/concern.json +0 -9
  159. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +0 -29
  160. package/rules/js-run/jsconfig/docs/index.md +0 -9
  161. package/rules/js-run/jsconfig/fix-jsconfig.mjs +0 -119
  162. package/rules/js-run/jsconfig/jsconfig.mdc +0 -48
  163. package/rules/js-run/jsconfig/jsconfig.rego +0 -59
  164. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +0 -10
  165. package/rules/js-run/lib/bunyan-imports.mjs +0 -98
  166. package/rules/js-run/lib/check-env-scan.mjs +0 -338
  167. package/rules/js-run/lib/conn-file-rules.mjs +0 -214
  168. package/rules/js-run/lib/conn-imports-scan.mjs +0 -154
  169. package/rules/js-run/lib/docs/bunyan-imports.md +0 -121
  170. package/rules/js-run/lib/docs/check-env-scan.md +0 -438
  171. package/rules/js-run/lib/docs/conn-file-rules.md +0 -304
  172. package/rules/js-run/lib/docs/conn-imports-scan.md +0 -208
  173. package/rules/js-run/lib/docs/index.md +0 -16
  174. package/rules/js-run/lib/docs/promise-settimeout-scan.md +0 -334
  175. package/rules/js-run/lib/docs/temporal-scan.md +0 -29
  176. package/rules/js-run/lib/promise-settimeout-scan.mjs +0 -128
  177. package/rules/js-run/lib/temporal-scan.mjs +0 -52
  178. package/rules/js-run/main.json +0 -1
  179. package/rules/js-run/main.mdc +0 -16
  180. package/rules/js-run/package_json/concern.json +0 -9
  181. package/rules/js-run/package_json/package_json.mdc +0 -44
  182. package/rules/js-run/package_json/package_json.rego +0 -37
  183. package/rules/js-run/package_json/template/package.json.deny.json +0 -22
  184. package/rules/js-run/project-structure/concern.json +0 -3
  185. package/rules/js-run/project-structure/project-structure.mdc +0 -11
  186. package/rules/js-run/runtime/concern.json +0 -12
  187. package/rules/js-run/runtime/docs/fix-runtime.md +0 -27
  188. package/rules/js-run/runtime/docs/main.md +0 -35
  189. package/rules/js-run/runtime/fix-runtime.mjs +0 -46
  190. package/rules/js-run/runtime/main.mjs +0 -494
  191. package/rules/js-run/runtime/runtime.mdc +0 -184
  192. package/rules/js-run/scope/concern.json +0 -3
  193. package/rules/js-run/scope/scope.mdc +0 -11
  194. package/rules/npm-module/docs/index.md +0 -11
  195. package/rules/npm-module/emit_types_config/concern.json +0 -9
  196. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +0 -25
  197. package/rules/npm-module/emit_types_config/docs/index.md +0 -9
  198. package/rules/npm-module/emit_types_config/emit_types_config.mdc +0 -43
  199. package/rules/npm-module/emit_types_config/emit_types_config.rego +0 -28
  200. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +0 -5
  201. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +0 -9
  202. package/rules/npm-module/header_doc_pointer/concern.json +0 -5
  203. package/rules/npm-module/header_doc_pointer/docs/main.md +0 -40
  204. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +0 -18
  205. package/rules/npm-module/header_doc_pointer/main.mjs +0 -131
  206. package/rules/npm-module/main.json +0 -1
  207. package/rules/npm-module/main.mdc +0 -36
  208. package/rules/npm-module/npm_package_json/concern.json +0 -9
  209. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +0 -27
  210. package/rules/npm-module/npm_package_json/docs/index.md +0 -9
  211. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +0 -5
  212. package/rules/npm-module/npm_package_json/npm_package_json.mdc +0 -57
  213. package/rules/npm-module/npm_package_json/npm_package_json.rego +0 -73
  214. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +0 -1
  215. package/rules/npm-module/package_structure/concern.json +0 -5
  216. package/rules/npm-module/package_structure/docs/main.md +0 -37
  217. package/rules/npm-module/package_structure/main.mjs +0 -515
  218. package/rules/npm-module/package_structure/package_structure.mdc +0 -63
  219. package/rules/npm-module/root_package_json/concern.json +0 -9
  220. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +0 -26
  221. package/rules/npm-module/root_package_json/docs/index.md +0 -9
  222. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +0 -5
  223. package/rules/npm-module/root_package_json/root_package_json.mdc +0 -41
  224. package/rules/npm-module/root_package_json/root_package_json.rego +0 -28
  225. package/rules/npm-module/root_package_json/template/package.json.snippet.json +0 -1
  226. package/rules/npm-module/rule_meta/concern.json +0 -8
  227. package/rules/npm-module/rule_meta/docs/main.md +0 -35
  228. package/rules/npm-module/rule_meta/main.mjs +0 -119
  229. package/rules/npm-module/rule_meta/rule_meta.mdc +0 -11
  230. package/rules/npm-module/skill_meta/concern.json +0 -5
  231. package/rules/npm-module/skill_meta/docs/main.md +0 -149
  232. package/rules/npm-module/skill_meta/main.mjs +0 -91
  233. package/rules/npm-module/skill_meta/skill_meta.mdc +0 -11
  234. package/rules/tool-surface/docs/index.md +0 -11
  235. package/rules/tool-surface/main.json +0 -6
  236. package/rules/tool-surface/main.mdc +0 -72
  237. package/rules/vue/composition-api/composition-api.mdc +0 -82
  238. package/rules/vue/composition-api/concern.json +0 -3
  239. package/rules/vue/docs/index.md +0 -11
  240. package/rules/vue/lib/docs/index.md +0 -11
  241. package/rules/vue/lib/docs/vue-forbidden-imports.md +0 -265
  242. package/rules/vue/lib/vue-forbidden-imports.mjs +0 -264
  243. package/rules/vue/main.json +0 -1
  244. package/rules/vue/main.mdc +0 -18
  245. package/rules/vue/nheader-layout/concern.json +0 -3
  246. package/rules/vue/nheader-layout/nheader-layout.mdc +0 -171
  247. package/rules/vue/package_json/concern.json +0 -9
  248. package/rules/vue/package_json/package_json.mdc +0 -30
  249. package/rules/vue/package_json/package_json.rego +0 -140
  250. package/rules/vue/packages/concern.json +0 -6
  251. package/rules/vue/packages/docs/index.md +0 -11
  252. package/rules/vue/packages/docs/main.md +0 -35
  253. package/rules/vue/packages/main.mjs +0 -575
  254. package/rules/vue/packages/packages.mdc +0 -56
  255. package/rules/vue/quasar-ui/concern.json +0 -3
  256. package/rules/vue/quasar-ui/quasar-ui.mdc +0 -32
  257. package/rules/vue/structure/concern.json +0 -3
  258. package/rules/vue/structure/structure.mdc +0 -101
  259. package/rules/vue/testing/concern.json +0 -3
  260. package/rules/vue/testing/testing.mdc +0 -40
  261. package/rules/vue/tfm-translations/concern.json +0 -7
  262. package/rules/vue/tfm-translations/docs/main.md +0 -29
  263. package/rules/vue/tfm-translations/main.mjs +0 -55
  264. package/rules/vue/tfm-translations/tfm-translations.mdc +0 -32
  265. package/rules/vue/vite-config/concern.json +0 -3
  266. package/rules/vue/vite-config/vite-config.mdc +0 -153
  267. package/rules/vue/vite-env/concern.json +0 -3
  268. package/rules/vue/vite-env/vite-env.mdc +0 -61
@@ -1,82 +0,0 @@
1
- ## Найкращі практики Vue 3 Composition API
2
-
3
- ```javascript
4
- const vue3CompositionApiBestPractices = [
5
- 'Використовуй функцію setup() для логіки компонента',
6
- 'Реалізуй computed змінні через $computed()',
7
- 'Реалізуй ref змінні через $ref',
8
- 'Використовуй watch і watchEffect для побічних ефектів',
9
- 'Підключай lifecycle hooks: onMounted, onUpdated тощо',
10
- 'Для глибоко вкладених залежностей використовуй composables, props/emits або store'
11
- 'не використовуй provide/inject для залежностей'
12
- ]
13
- ```
14
-
15
- ### Патерни та антипатерни
16
-
17
- - Для глибоко вкладених залежностей використовуй **composables**, **props/emits** або **store**; **renderless**-компоненти / **slots** — коли логіку відділяєш від розмітки.
18
- - **HTTP:** окремі модулі **services** або **composables** для API; **async/await**.
19
- - **Події:** батько–дитина через **emits**; для не пов'язаних гілок — **store**.
20
- - Не мутуй **props** напряму — оновлення через подію вгору або v-model.
21
- - Обмежуй зайве в глобальному стані; локальний стан у компоненті — за замовчуванням.
22
- - Уникай прямої роботи з **DOM**, якщо достатньо реактивного шаблону та ref.
23
-
24
- ### State management
25
-
26
- - **Single source of truth** для спільних даних: у нових проєктах на Vue 3 **Pinia** (модульні stores, actions).
27
- - Похідний стан — через обчислення в store або **computed** у компонентах, без «тихих» побічних ефектів у getters.
28
-
29
- ### Обробка помилок
30
-
31
- - **try/catch** навколо async-операцій; зрозумілі повідомлення для користувача через notifySuccess, notifyError; логування на сервіс моніторингу за потреби.
32
-
33
- ### Продуктивність
34
-
35
- - **v-for** — стабільні унікальні **`:key`**; не плутай **v-if** (умовний mount) і **v-show** (перемикання visibility).
36
- - **debounce/throttle** для частих подій.
37
- - Після ручних **addEventListener** / підписок — прибирай у **onUnmounted**.
38
-
39
- ### Функції в шаблоні
40
-
41
- Виклики функцій у шаблоні дозволені **лише** в обробниках подій (`@click`, `@change` тощо). У всіх інших місцях — `v-if`, `v-show`, атрибутах (`:prop`), інтерполяціях (`{{ }}`) — замінюй функції на `computed`-властивості: функція виконується при **кожному** render-і, тоді як `computed` кешується і перераховується лише при зміні залежностей.
42
-
43
- ```vue
44
- <!-- ❌ функція в умові, атрибуті та інтерполяції -->
45
- <q-item v-if="getItems(order).length" :label="getLabel(item)">
46
- {{ formatName(user) }}
47
- </q-item>
48
-
49
- <!-- ✅ реактивні змінні / computed / props -->
50
- <q-item v-if="itemsMap[order.id].length" :label="item.label">
51
- {{ user.displayName }}
52
- </q-item>
53
- <!-- обробник події — виклик функції дозволений -->
54
- <q-btn @click="doSomething(item)" />
55
- ```
56
-
57
- ### Безпека
58
-
59
- - Не довіряй **v-html** без санітизації; для форм/API — **CSRF**-захист за потреби; валідація **на сервері** обов'язкова.
60
-
61
- ### Приклад компонента
62
-
63
- ```javascript
64
- // Приклад Vue 3 компонента з Composition API
65
- import { computed, onMounted } from 'vue'
66
-
67
- export default {
68
- setup() {
69
- const count = $ref(0)
70
- const doubleCount = $computed(() => count * 2)
71
-
72
- onMounted(() => {
73
- console.log('Компонент змонтовано')
74
- })
75
-
76
- return {
77
- count,
78
- doubleCount
79
- }
80
- }
81
- }
82
- ```
@@ -1,3 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
- }
@@ -1,11 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/vue
4
- resource: npm/rules/vue/
5
- ---
6
-
7
- # npm/rules/vue
8
-
9
- | Файл | Тип |
10
- | ------------------- | --------- |
11
- | [main.mjs](main.md) | JS Module |
@@ -1,11 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/vue/lib
4
- resource: npm/rules/vue/lib/
5
- ---
6
-
7
- # npm/rules/vue/lib
8
-
9
- | Файл | Тип |
10
- | ----------------------------------------------------- | --------- |
11
- | [vue-forbidden-imports.mjs](vue-forbidden-imports.md) | JS Module |
@@ -1,265 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: vue-forbidden-imports.mjs
4
- resource: npm/rules/vue/lib/vue-forbidden-imports.mjs
5
- docgen:
6
- crc: 653c6412
7
- ---
8
-
9
- Модуль `vue-forbidden-imports.mjs` — це бібліотека статичного аналізу `import`-декларацій, призначена для виявлення двох категорій порушень у вихідному коді Vue-проєкту:
10
-
11
- 1. **Явні (runtime) імпорти з модуля `vue`** у будь-яких файлах, що сканує правило. За конвенцією `vue.mdc` у проєкті працює `unplugin-auto-import`, тому імпорти `ref`, `computed`, `watch` тощо мають бути неявними. Дозволено лише: side-effect форму (`import 'vue'`), повністю type-only імпорти (`import type { ... } from 'vue'`) та змішані форми, де **всі** іменовані записи мають флаг `isType` (наприклад, `import { type A, type B } from 'vue'`).
12
- 2. **Імпорти Node-нативних модулів усередині `.vue` SFC** — `node:fs`, `node:timers/promises`, а також bare-форми вбудованих модулів (`fs`, `path`, `crypto`, `fs/promises` тощо). Vue Single-File Components виконуються в браузерному середовищі, де Node API недоступне, тому такі імпорти зривають збірку чи призводять до runtime-помилок.
13
-
14
- Аналіз виконується через **oxc-parser** (`parseSync`) — ESTree-сумісний AST-парсер, що повертає об'єкт із полем `module.staticImports`. Це усуває потребу в крихких регулярних виразах для розпізнавання структури імпортів і коректно обробляє TypeScript-синтаксис (включно з type-only записами через флаг `entries[].isType`).
15
-
16
- Для `.vue` файлів виконується попередній етап: регулярним виразом витягуються вмісти всіх тегів `<script>` / `<script setup>` (template ігнорується), і вже цей конкатенований код подається парсеру з віртуальним ім'ям `*.ts`, щоб увімкнути TypeScript-режим.
17
-
18
- Модуль чисто функціональний: жодних звернень до файлової системи, мережі чи глобального стану — усі функції приймають уже прочитаний контент і повертають структуровані дані про порушення.
19
-
20
- ## Експорти / API
21
-
22
- Усі експорти — іменовані (named exports), default export відсутній.
23
-
24
- | Експорт | Тип | Призначення |
25
- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------ |
26
- | `extractVueScriptBlocks(sfc)` | function | Витягує конкатенований код усіх `<script>` блоків з `.vue` SFC. |
27
- | `contentForVueImportScan(content, filePath)` | function | Повертає текст для сканування: для `.vue` — лише script-блоки, інакше — увесь вміст. |
28
- | `findForbiddenVueImportsInText(content, virtualPath?)` | function | Знаходить заборонені static-імпорти з `vue` у вже підготовленому тексті. |
29
- | `shouldSkipFileForVueImportScan(relativePosix)` | function | Чи пропустити файл під час обходу пакета (генерація, `.d.ts`). |
30
- | `isVueImportScanSourceFile(relativePath)` | function | Чи розширення файлу підходить для сканування. |
31
- | `findForbiddenVueImportsInSourceFile(content, relativePath)` | function | Об'єднує підготовку контенту та парсинг для одного файлу. |
32
- | `isNodeBuiltinSpecifier(spec)` | function | Чи специфікатор імпорту відповідає Node-нативному модулю. |
33
- | `findForbiddenNodeImportsInText(content, virtualPath?)` | function | Знаходить заборонені Node-імпорти у тексті. |
34
- | `findForbiddenNodeImportsInVueFile(content, relativePath)` | function | Знаходить заборонені Node-імпорти лише у `.vue` файлах (template ігнорується). |
35
-
36
- Внутрішні (не експортовані) допоміжні функції: `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isAllowedVueStaticImport`, `virtualPathForParse`.
37
-
38
- Внутрішні (не експортовані) константи:
39
-
40
- - `NODE_BUILTIN_MODULES` — `Set<string>` з повного списку `builtinModules` Node.js на момент запуску (наприклад, `fs`, `path`, `crypto`, ...).
41
- - `VUE_EXT_RE` — регекс `/\.vue$/u` для перевірки розширення `.vue`.
42
- - `SOURCE_FILE_RE` — регекс `/\.(vue|[cm]?[jt]sx?)$/`, що покриває `.vue`, `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`.
43
-
44
- ## Функції
45
-
46
- ### `langFromPath(filePath)` — внутрішня
47
-
48
- - **Сигнатура:** `(filePath: string) => 'js' | 'jsx' | 'ts' | 'tsx'`
49
- - **Параметри:**
50
- - `filePath` — віртуальний або реальний шлях; розпізнавання йде за суфіксом у lowercase.
51
- - **Повертає:** значення опції `lang` для `parseSync`. Розширення `.ts`, `.mts`, `.cts` → `'ts'`; `.tsx` → `'tsx'`; `.jsx` → `'jsx'`; решта (включно з `.js`, `.mjs`, `.cjs`, відсутність розширення) → `'js'`.
52
- - **Side effects:** немає.
53
-
54
- ### `offsetToLine(content, offset)` — внутрішня
55
-
56
- - **Сигнатура:** `(content: string, offset: number) => number`
57
- - **Параметри:**
58
- - `content` — повний текст файлу.
59
- - `offset` — байтове зміщення (точніше — індекс кодової одиниці UTF-16) початку фрагмента.
60
- - **Повертає:** 1-based номер рядка для зміщення. Алгоритм лінійно проходить діапазон `[0, min(offset, content.length))` і збільшує лічильник для кожного `\n` (code point `10`).
61
- - **Side effects:** немає.
62
-
63
- ### `normalizeSnippet(s)` — внутрішня
64
-
65
- - **Сигнатура:** `(s: string) => string`
66
- - **Параметри:**
67
- - `s` — довільний фрагмент коду.
68
- - **Повертає:** однорядковий рядок: усі послідовності whitespace замінено одним пробілом, обрізано початкові/кінцеві пробіли, обмежено перші 160 символів.
69
- - **Side effects:** немає.
70
-
71
- ### `isAllowedVueStaticImport(imp)` — внутрішня
72
-
73
- - **Сигнатура:** `(imp: { moduleRequest: { value: string }, entries: { isType: boolean }[] }) => boolean`
74
- - **Параметри:**
75
- - `imp` — один запис із масиву `module.staticImports`, отриманого від `parseSync`.
76
- - **Повертає:** `true`, якщо:
77
- - `entries.length === 0` (це `import 'vue'` — side-effect форма), **або**
78
- - усі `entries` мають `isType === true` (повністю type-only або змішана форма з `import { type X } from 'vue'`).
79
- - **Side effects:** немає.
80
-
81
- ### `extractVueScriptBlocks(sfc)` — експортована
82
-
83
- - **Сигнатура:** `(sfc: string) => string`
84
- - **Параметри:**
85
- - `sfc` — повний вміст `.vue` файлу.
86
- - **Повертає:** конкатенацію (роздільник `\n\n`) тіл усіх знайдених `<script>` блоків. Регекс `/<script\b[^>]*>([\s\S]*?)<\/script>/gi` дозволяє атрибути після `<script` (наприклад, `setup`, `lang="ts"`, `generic="T"`) і не чіпає шаблон `<template>` чи стилі `<style>`.
87
- - **Side effects:** немає.
88
-
89
- ### `contentForVueImportScan(content, filePath)` — експортована
90
-
91
- - **Сигнатура:** `(content: string, filePath: string) => string`
92
- - **Параметри:**
93
- - `content` — сирий вміст файлу.
94
- - `filePath` — шлях, потрібний лише для перевірки суфікса `.vue`.
95
- - **Повертає:** для `.vue` — результат `extractVueScriptBlocks(content)`; інакше — `content` без змін.
96
- - **Side effects:** немає.
97
-
98
- ### `virtualPathForParse(relativePath)` — внутрішня
99
-
100
- - **Сигнатура:** `(relativePath: string) => string`
101
- - **Параметри:**
102
- - `relativePath` — шлях файлу в пакеті/репо.
103
- - **Повертає:** якщо суфікс `.vue` — той самий шлях із заміною `.vue` на `.ts` (для `langFromPath` → `'ts'`); інакше — шлях без змін.
104
- - **Side effects:** немає.
105
-
106
- ### `findForbiddenVueImportsInText(content, virtualPath?)` — експортована
107
-
108
- - **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
109
- - **Параметри:**
110
- - `content` — вже підготовлений текст для парсингу (для `.vue` — лише `<script>` блоки).
111
- - `virtualPath` — необов'язковий шлях для вибору `lang`. Значення за замовчуванням — `'scan.ts'` (TypeScript-режим). Якщо передано порожнє/falsy значення — використовується той самий fallback `'scan.ts'`.
112
- - **Повертає:** масив об'єктів `{ line, snippet }`, де:
113
- - `line` — 1-based номер рядка, де починається `import`,
114
- - `snippet` — стиснений однорядковий фрагмент тексту `content.slice(imp.start, imp.end)` (≤ 160 символів).
115
- - **Поведінка:**
116
- - У разі будь-якої exception з `parseSync` (`try/catch`) повертає `[]`.
117
- - Якщо `result.errors?.length > 0` — повертає `[]` (тобто за наявності синтаксичних помилок порушення не репортяться; коментар у файлі прямо радить «спочатку виправ синтаксис»).
118
- - Інакше проходить `result.module.staticImports` і додає в результат запис, якщо `moduleRequest.value === 'vue'` **і** `!isAllowedVueStaticImport(imp)`.
119
- - **Side effects:** немає (читання Node-builtins трапилось лише на старті модуля).
120
-
121
- ### `shouldSkipFileForVueImportScan(relativePosix)` — експортована
122
-
123
- - **Сигнатура:** `(relativePosix: string) => boolean`
124
- - **Параметри:**
125
- - `relativePosix` — шлях файлу з posix-слешами (форвард-слеш як роздільник).
126
- - **Повертає:** `true`, якщо:
127
- - basename дорівнює `auto-imports.d.ts` або `components.d.ts` (типові згенеровані файли від `unplugin-auto-import` / `unplugin-vue-components`), **або**
128
- - шлях закінчується на `.d.ts` (будь-який type-declaration файл).
129
- - **Side effects:** немає.
130
-
131
- ### `isVueImportScanSourceFile(relativePath)` — експортована
132
-
133
- - **Сигнатура:** `(relativePath: string) => boolean`
134
- - **Параметри:**
135
- - `relativePath` — відносний шлях.
136
- - **Повертає:** `true`, якщо суфікс файлу відповідає `SOURCE_FILE_RE` (тобто `.vue`, `.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`).
137
- - **Side effects:** немає.
138
-
139
- ### `findForbiddenVueImportsInSourceFile(content, relativePath)` — експортована
140
-
141
- - **Сигнатура:** `(content: string, relativePath: string) => { line: number, snippet: string }[]`
142
- - **Параметри:**
143
- - `content` — сирий вміст файлу.
144
- - `relativePath` — шлях відносно кореня пакета чи репо.
145
- - **Повертає:** результат `findForbiddenVueImportsInText(scan, virtualPath)`, де:
146
- - `scan = contentForVueImportScan(content, relativePath)` — для `.vue` витягнуті `<script>` блоки, для решти — `content`;
147
- - `virtualPath = virtualPathForParse(relativePath)` — для `.vue` замінено суфікс на `.ts`, для решти — без змін.
148
- - **Side effects:** немає.
149
-
150
- ### `isNodeBuiltinSpecifier(spec)` — експортована
151
-
152
- - **Сигнатура:** `(spec: string) => boolean`
153
- - **Параметри:**
154
- - `spec` — значення `moduleRequest.value` (текст специфікатора імпорту).
155
- - **Повертає:** `true`, якщо специфікатор — Node-нативний модуль. Послідовність перевірок:
156
- 1. Якщо `spec` не рядок або порожній → `false`.
157
- 2. Префікс `node:` → `true` (покриває `node:fs`, `node:timers/promises`, `node:test` тощо).
158
- 3. Точне співпадіння в `NODE_BUILTIN_MODULES` → `true` (наприклад, `fs`, `path`, `crypto`).
159
- 4. Якщо є слеш на позиції > 0 — перевірити «head» (`spec.slice(0, slashIdx)`); якщо head у `NODE_BUILTIN_MODULES` → `true` (покриває підшляхи на кшталт `fs/promises`, `stream/web`, `timers/promises`).
160
- 5. Інакше → `false`.
161
- - **Side effects:** немає.
162
-
163
- ### `findForbiddenNodeImportsInText(content, virtualPath?)` — експортована
164
-
165
- - **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string, specifier: string }[]`
166
- - **Параметри:**
167
- - `content` — підготовлений текст (для `.vue` — `<script>` блоки).
168
- - `virtualPath` — шлях для вибору `lang`. Default — `'scan.ts'`; при falsy використовується той самий fallback.
169
- - **Повертає:** масив `{ line, snippet, specifier }`. Структура `line` / `snippet` ідентична `findForbiddenVueImportsInText`; поле `specifier` — це сирий рядок з `imp.moduleRequest.value` (наприклад, `'node:fs'` або `'fs/promises'`).
170
- - **Поведінка:**
171
- - Парсинг через `parseSync(pathForLang, content, { lang, sourceType: 'module' })` всередині `try/catch` — будь-яка exception → `[]`.
172
- - Якщо `result.errors?.length > 0` → `[]`.
173
- - Для кожного `imp` із `result.module.staticImports` перевіряється `isNodeBuiltinSpecifier(spec)`; при `true` додається запис у результат.
174
- - Зверніть увагу: правило репортить **усі** Node-імпорти у Vue-контексті, у тому числі type-only (`import type { Stats } from 'fs'`) — коментар у вихідному коді пояснює, що type-only імпорти Node-модулів у SFC заплутують і доцільніше тримати такий код у server-side утилітах.
175
- - **Side effects:** немає.
176
-
177
- ### `findForbiddenNodeImportsInVueFile(content, relativePath)` — експортована
178
-
179
- - **Сигнатура:** `(content: string, relativePath: string) => { line: number, snippet: string, specifier: string }[]`
180
- - **Параметри:**
181
- - `content` — сирий вміст файлу.
182
- - `relativePath` — шлях відносно кореня пакета чи репо.
183
- - **Повертає:**
184
- - Якщо суфікс не `.vue` → `[]` (правило стосується лише SFC; композаблі та утиліти на Node-side можуть жити у `.ts`/`.js`).
185
- - Інакше: `findForbiddenNodeImportsInText(extractVueScriptBlocks(content), virtualPathForParse(relativePath))`.
186
- - **Side effects:** немає.
187
-
188
- ## Залежності
189
-
190
- ### Зовнішні модулі
191
-
192
- - **`node:module` (Node.js builtin)** — імпортується `builtinModules` для формування `NODE_BUILTIN_MODULES`. Список фіксується на момент запуску модуля (одноразово).
193
- - **`oxc-parser`** — `parseSync(filename, source, options)`. Очікувані опції:
194
- - `lang: 'js' | 'jsx' | 'ts' | 'tsx'` — обирається `langFromPath`;
195
- - `sourceType: 'module'` — фіксовано як ES-module.
196
- - Результат використовується через:
197
- - `result.errors` — масив синтаксичних помилок; за наявності повертається `[]`;
198
- - `result.module.staticImports` — масив записів виду `{ moduleRequest: { value }, entries: [{ isType }], start, end }`.
199
-
200
- ### Внутрішні залежності між функціями цього файлу
201
-
202
- - `findForbiddenVueImportsInSourceFile` → `contentForVueImportScan`, `virtualPathForParse`, `findForbiddenVueImportsInText`.
203
- - `findForbiddenVueImportsInText` → `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isAllowedVueStaticImport`.
204
- - `findForbiddenNodeImportsInVueFile` → `extractVueScriptBlocks`, `virtualPathForParse`, `findForbiddenNodeImportsInText`.
205
- - `findForbiddenNodeImportsInText` → `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isNodeBuiltinSpecifier`.
206
- - `contentForVueImportScan` → `extractVueScriptBlocks`.
207
- - `isNodeBuiltinSpecifier` → `NODE_BUILTIN_MODULES`.
208
-
209
- ### Зовнішні споживачі (за конвенцією)
210
-
211
- Файл лежить у `npm/rules/vue/lib/` і призначений для використання з `check-*.mjs` сценаріїв правила `n-vue`-родини. Сценарії-перевірки самі обходять пакети, читають файли з диска, фільтрують їх через `shouldSkipFileForVueImportScan` та `isVueImportScanSourceFile`, і викликають `findForbiddenVueImportsInSourceFile` / `findForbiddenNodeImportsInVueFile`.
212
-
213
- ## Потік виконання / Використання
214
-
215
- ### Типовий потік для сканера-перевірки
216
-
217
- 1. Сценарій-перевірка обходить файли в пакеті, отримуючи `(relativePath, absolutePath)`.
218
- 2. Фільтрація:
219
- - `if (shouldSkipFileForVueImportScan(relativePosix)) continue;` — пропустити `.d.ts` і згенеровані файли.
220
- - `if (!isVueImportScanSourceFile(relativePath)) continue;` — пропустити не-source файли (скажімо, `.json`, `.md`).
221
- 3. Прочитати `content = fs.readFileSync(absolutePath, 'utf8')`.
222
- 4. Знайти порушення:
223
- - `const vueViolations = findForbiddenVueImportsInSourceFile(content, relativePath);`
224
- - `const nodeViolations = findForbiddenNodeImportsInVueFile(content, relativePath);`
225
- 5. Якщо масиви непорожні — зрепортити користувачу: `relativePath:${line}` + `snippet` (+ `specifier` для Node-порушень).
226
-
227
- ### Точкове використання
228
-
229
- - **Тільки сирий код (без файлів):** виклик `findForbiddenVueImportsInText(code, 'scan.ts')` або `findForbiddenNodeImportsInText(code, 'scan.ts')` напряму — корисно для unit-тестів модуля.
230
- - **Лише витяг `<script>` блоків:** `extractVueScriptBlocks(sfc)` повертає конкатенований код; зручно для інших сканерів, що працюють лише з JS/TS-частиною SFC.
231
-
232
- ### Контракти й тонкі моменти
233
-
234
- - **Помилки парсингу = «не репортимо».** Якщо `parseSync` кинув exception **або** повернув `result.errors.length > 0`, обидві `find*InText` функції повертають `[]`. Це навмисний дизайн: правило не намагається лагодити синтаксис — спочатку файл має бути коректним, тоді сканер дасть осмислений вихід.
235
- - **Default `virtualPath = 'scan.ts'`.** Без передачі `virtualPath` режим парсингу — TypeScript. Це безпечно і для чистого JS (TS-парсер приймає JS-синтаксис), і дозволяє type-only синтаксис.
236
- - **Type-only синтаксис у `vue` дозволено.** `import type { Ref } from 'vue'` і `import { type Ref } from 'vue'` пройдуть перевірку через `entries[].isType === true`. Це не суперечить ідеї auto-import: типи завжди потрібно імпортувати явно.
237
- - **Type-only синтаксис у Node-імпортах НЕ дозволено.** Для `.vue` навіть `import type { Stats } from 'fs'` буде в результаті — модуль не розглядає `isType` для Node-перевірки.
238
- - **`offsetToLine` рахує `\n` посимвольно.** Складність `O(offset)`. На великих файлах із багатьма імпортами це сумарно `O(N·M)`, але на практиці прийнятно (файли SFC рідко > 10k рядків).
239
- - **`normalizeSnippet` обмежує 160 символів** і стискає whitespace — формат повідомлення про порушення стабільний, незалежно від форматування у коді.
240
-
241
- ## Rebuild Test
242
-
243
- За цією документацією має бути можливо відновити функціональний еквівалент модуля. Контрольні точки відтворення:
244
-
245
- - **Експорти:** `extractVueScriptBlocks`, `contentForVueImportScan`, `findForbiddenVueImportsInText`, `shouldSkipFileForVueImportScan`, `isVueImportScanSourceFile`, `findForbiddenVueImportsInSourceFile`, `isNodeBuiltinSpecifier`, `findForbiddenNodeImportsInText`, `findForbiddenNodeImportsInVueFile` — усі named, default відсутній.
246
- - **Регулярні вирази:**
247
- - `<script>` екстрактор: `/<script\b[^>]*>([\s\S]*?)<\/script>/gi`.
248
- - `.vue` суфікс: `/\.vue$/u`.
249
- - Source files: `/\.(vue|[cm]?[jt]sx?)$/`.
250
- - **Skip-файли:** basename `auto-imports.d.ts` / `components.d.ts` або суфікс `.d.ts`.
251
- - **`langFromPath` мапінг:** `.tsx`→`tsx`; `.ts|.mts|.cts`→`ts`; `.jsx`→`jsx`; default→`js` (через lowercase порівняння суфіксів).
252
- - **Vue allow-list:** порожній `entries` (side-effect `import 'vue'`) або `entries.every(e => e.isType)`.
253
- - **Node-builtin перевірка:**
254
- 1. Не рядок або порожній → `false`.
255
- 2. `startsWith('node:')` → `true`.
256
- 3. У `Set(builtinModules)` → `true`.
257
- 4. Якщо є слеш (індекс > 0) і head у Set → `true`.
258
- 5. Інакше → `false`.
259
- - **Парсер:** `parseSync(virtualPath, content, { lang, sourceType: 'module' })`; обробка `try/catch` + перевірка `result.errors?.length`.
260
- - **Результат-формат:** Vue-порушення — `{ line, snippet }`; Node-порушення — `{ line, snippet, specifier }`.
261
- - **Default `virtualPath`:** `'scan.ts'` (включно з fallback при falsy значенні).
262
- - **`findForbiddenNodeImportsInVueFile` гарантія:** для не-`.vue` повертає `[]` навіть якщо файл містить Node-імпорти.
263
- - **`virtualPathForParse`:** `.vue` → той самий шлях з суфіксом `.ts`; інакше — без змін.
264
- - **Snippet:** `replaceAll(/\s+/g, ' ').trim().slice(0, 160)`.
265
- - **`offsetToLine`:** 1-based, лічить `\n` (code point 10) у діапазоні `[0, min(offset, length))`.
@@ -1,264 +0,0 @@
1
- /**
2
- * Визначає явні імпорти з модуля `vue`, які суперечать vue.mdc (має працювати unplugin-auto-import),
3
- * а також заборонені імпорти Node-нативних модулів у `.vue` SFC (`node:*` префікс або bare ім’я
4
- * вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Vue SFC виконується у браузері,
5
- * де Node API недоступне, тож такі імпорти ламають збірку/рантайм.
6
- *
7
- * Аналіз import виконується через **oxc-parser** (`parseSync`, поле `module.staticImports`) — ESTree-сумісний
8
- * розбір без евристик по рядках. Дозволені лише side-effect `import 'vue'`, повністю type-only імпорти
9
- * та `import { type A, type B } from 'vue'` (перевірка `entries[].isType`).
10
- *
11
- * Для `.vue` з шаблону витягуються лише теги `<script>` / `<script setup>` (регулярний вираз); далі той самий Oxc-парсинг
12
- * вмісту скрипта з віртуальним ім’ям `*.ts` для режиму TypeScript.
13
- */
14
- import { builtinModules } from 'node:module'
15
-
16
- import { parseSync } from 'oxc-parser'
17
-
18
- const NODE_BUILTIN_MODULES = new Set(builtinModules)
19
-
20
- const VUE_EXT_RE = /\.vue$/u
21
- const SOURCE_FILE_RE = /\.(vue|[cm]?[jt]sx?)$/
22
-
23
- /**
24
- * Мова для Oxc за шляхом файлу (розширення).
25
- * @param {string} filePath віртуальний або реальний шлях до файлу
26
- * @returns {'js' | 'jsx' | 'ts' | 'tsx'} значення опції `lang` для `parseSync`
27
- */
28
- function langFromPath(filePath) {
29
- const lower = filePath.toLowerCase()
30
- if (lower.endsWith('.tsx')) {
31
- return 'tsx'
32
- }
33
- if (lower.endsWith('.ts') || lower.endsWith('.mts') || lower.endsWith('.cts')) {
34
- return 'ts'
35
- }
36
- if (lower.endsWith('.jsx')) {
37
- return 'jsx'
38
- }
39
- return 'js'
40
- }
41
-
42
- /**
43
- * Номер рядка (1-based) за зміщенням у буфері.
44
- * @param {string} content повний текст файлу
45
- * @param {number} offset байтове зміщення початку import
46
- * @returns {number} номер рядка від 1
47
- */
48
- function offsetToLine(content, offset) {
49
- let line = 1
50
- const n = Math.min(offset, content.length)
51
- for (let i = 0; i < n; i++) {
52
- if (content.codePointAt(i) === 10) {
53
- line++
54
- }
55
- }
56
- return line
57
- }
58
-
59
- /**
60
- * Стискає пробіли для повідомлення про порушення.
61
- * @param {string} s фрагмент коду
62
- * @returns {string} скорочений однорядковий рядок
63
- */
64
- function normalizeSnippet(s) {
65
- return s.replaceAll(/\s+/g, ' ').trim().slice(0, 160)
66
- }
67
-
68
- /**
69
- * Чи цей static import з `vue` дозволено правилом (усі записи type-only або порожній side-effect).
70
- * @param {{ moduleRequest: { value: string }, entries: { isType: boolean }[] }} imp запис з `module.staticImports`
71
- * @returns {boolean} `true`, якщо імпорт дозволено (type-only або `import 'vue'`)
72
- */
73
- function isAllowedVueStaticImport(imp) {
74
- if (imp.entries.length === 0) {
75
- return true
76
- }
77
- return imp.entries.every(e => e.isType)
78
- }
79
-
80
- /**
81
- * Віртуальний шлях для парсера: вміст з `<script>` у `.vue` розбираємо як TypeScript.
82
- * @param {string} relativePath шлях до файлу в пакеті
83
- * @returns {string} той самий шлях або з `.vue` заміненим на `.ts`
84
- */
85
- function virtualPathForParse(relativePath) {
86
- if (relativePath.endsWith('.vue')) {
87
- return relativePath.replace(VUE_EXT_RE, '.ts')
88
- }
89
- return relativePath
90
- }
91
-
92
- /**
93
- * Витягує з SFC лише код усередині `<script>`, щоб не чіпати шаблон.
94
- * @param {string} sfc вміст .vue файлу
95
- * @returns {string} текст усередині тегів `<script>` (усі блоки поспіль)
96
- */
97
- export function extractVueScriptBlocks(sfc) {
98
- const chunks = []
99
- const re = /<script\b[^>]*>([\s\S]*?)<\/script>/gi
100
- let m = re.exec(sfc)
101
- while (m) {
102
- chunks.push(m[1])
103
- m = re.exec(sfc)
104
- }
105
- return chunks.join('\n\n')
106
- }
107
-
108
- /**
109
- * Підбирає текст для сканування: для .vue — лише script-блоки, інакше — увесь вміст.
110
- * @param {string} content сирий вміст файлу
111
- * @param {string} filePath відносний шлях (для вибору режиму)
112
- * @returns {string} текст для `parseSync`
113
- */
114
- export function contentForVueImportScan(content, filePath) {
115
- if (filePath.endsWith('.vue')) {
116
- return extractVueScriptBlocks(content)
117
- }
118
- return content
119
- }
120
-
121
- /**
122
- * Знаходить заборонені static import з `vue` у вже підготовленому тексті (без `<template>`).
123
- * Використовує **oxc-parser**; при синтаксичних помилках повертає порожній масив (спочатку виправ синтаксис).
124
- * @param {string} content вихідний код
125
- * @param {string} [virtualPath] шлях для вибору `lang` (наприклад `app/src/foo.ts` або віртуальний після `.vue` → `.ts`)
126
- * @returns {{ line: number, snippet: string }[]} список порушень з номером рядка початку import
127
- */
128
- export function findForbiddenVueImportsInText(content, virtualPath = 'scan.ts') {
129
- const pathForLang = virtualPath || 'scan.ts'
130
- const lang = langFromPath(pathForLang)
131
- let result
132
- try {
133
- result = parseSync(pathForLang, content, { lang, sourceType: 'module' })
134
- } catch {
135
- return []
136
- }
137
- if (result.errors?.length) {
138
- return []
139
- }
140
- /** @type {{ line: number, snippet: string }[]} */
141
- const out = []
142
- for (const imp of result.module.staticImports) {
143
- if (imp.moduleRequest.value === 'vue' && !isAllowedVueStaticImport(imp)) {
144
- out.push({
145
- line: offsetToLine(content, imp.start),
146
- snippet: normalizeSnippet(content.slice(imp.start, imp.end))
147
- })
148
- }
149
- }
150
- return out
151
- }
152
-
153
- /**
154
- * Чи слід пропустити файл під час обходу пакета (генерація, типи).
155
- * @param {string} relativePosix шлях з posix-слешами
156
- * @returns {boolean} `true`, якщо файл не сканувати (`.d.ts`, згенеровані імена)
157
- */
158
- export function shouldSkipFileForVueImportScan(relativePosix) {
159
- const base = relativePosix.split('/').pop() || ''
160
- if (base === 'auto-imports.d.ts' || base === 'components.d.ts') {
161
- return true
162
- }
163
- return Boolean(relativePosix.endsWith('.d.ts'))
164
- }
165
-
166
- /**
167
- * Чи сканувати цей файл за розширенням.
168
- * @param {string} relativePath відносний шлях до файлу
169
- * @returns {boolean} `true`, якщо розширення підходить для пошуку import
170
- */
171
- export function isVueImportScanSourceFile(relativePath) {
172
- return SOURCE_FILE_RE.test(relativePath)
173
- }
174
-
175
- /**
176
- * Знаходить порушення в одному файлі (з урахуванням .vue script extraction).
177
- * @param {string} content сирий вміст файлу
178
- * @param {string} relativePath шлях відносно кореня пакета або репо
179
- * @returns {{ line: number, snippet: string }[]} список порушень для цього файлу
180
- */
181
- export function findForbiddenVueImportsInSourceFile(content, relativePath) {
182
- const scan = contentForVueImportScan(content, relativePath)
183
- const virtualPath = virtualPathForParse(relativePath)
184
- return findForbiddenVueImportsInText(scan, virtualPath)
185
- }
186
-
187
- /**
188
- * Чи є рядок-специфікатор імпортом вбудованого Node-модуля.
189
- * Покриває обидві форми: `node:fs`, `node:timers/promises` (явний префікс) і bare-ім’я
190
- * вбудованого модуля (`fs`, `path`, `crypto` тощо), включно з підшляхами (`fs/promises`).
191
- * @param {string} spec значення `moduleRequest.value` (специфікатор імпорту)
192
- * @returns {boolean} `true`, якщо це Node-нативний модуль
193
- */
194
- export function isNodeBuiltinSpecifier(spec) {
195
- if (typeof spec !== 'string' || spec.length === 0) {
196
- return false
197
- }
198
- if (spec.startsWith('node:')) {
199
- return true
200
- }
201
- if (NODE_BUILTIN_MODULES.has(spec)) {
202
- return true
203
- }
204
- const slashIdx = spec.indexOf('/')
205
- if (slashIdx > 0) {
206
- const head = spec.slice(0, slashIdx)
207
- if (NODE_BUILTIN_MODULES.has(head)) {
208
- return true
209
- }
210
- }
211
- return false
212
- }
213
-
214
- /**
215
- * Знаходить заборонені імпорти Node-нативних модулів у вмісті (без `<template>`).
216
- * Vue SFC виконується у браузері, тож будь-який Node API там недоступний — навіть type-only
217
- * імпорти збивають з пантелику (краще тримати такий код у server-side утілітах).
218
- * @param {string} content вихідний код (для `.vue` — вже витягнуті `<script>` блоки)
219
- * @param {string} [virtualPath] шлях для вибору `lang` (наприклад віртуальний `*.ts` після `.vue`)
220
- * @returns {{ line: number, snippet: string, specifier: string }[]} список порушень з номером рядка
221
- */
222
- export function findForbiddenNodeImportsInText(content, virtualPath = 'scan.ts') {
223
- const pathForLang = virtualPath || 'scan.ts'
224
- const lang = langFromPath(pathForLang)
225
- let result
226
- try {
227
- result = parseSync(pathForLang, content, { lang, sourceType: 'module' })
228
- } catch {
229
- return []
230
- }
231
- if (result.errors?.length) {
232
- return []
233
- }
234
- /** @type {{ line: number, snippet: string, specifier: string }[]} */
235
- const out = []
236
- for (const imp of result.module.staticImports) {
237
- const spec = imp.moduleRequest.value
238
- if (isNodeBuiltinSpecifier(spec)) {
239
- out.push({
240
- line: offsetToLine(content, imp.start),
241
- snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
242
- specifier: spec
243
- })
244
- }
245
- }
246
- return out
247
- }
248
-
249
- /**
250
- * Знаходить заборонені імпорти Node-нативних модулів у `.vue` SFC.
251
- * Сканує лише `<script>` блоки (template ігноруємо). Для не-`.vue` файлів повертає `[]` —
252
- * композаблі/утіліти на Node-side можуть бути в `.ts`/`.js`, а правило стосується SFC.
253
- * @param {string} content сирий вміст файлу
254
- * @param {string} relativePath шлях відносно кореня пакета або репо
255
- * @returns {{ line: number, snippet: string, specifier: string }[]} список порушень
256
- */
257
- export function findForbiddenNodeImportsInVueFile(content, relativePath) {
258
- if (!relativePath.endsWith('.vue')) {
259
- return []
260
- }
261
- const scan = extractVueScriptBlocks(content)
262
- const virtualPath = virtualPathForParse(relativePath)
263
- return findForbiddenNodeImportsInText(scan, virtualPath)
264
- }
@@ -1 +0,0 @@
1
- { "auto": { "glob": "**/*.vue" } }