@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,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/tool-surface
4
+ resource: plugins/lang-js/rules/tool-surface/
5
+ ---
6
+
7
+ # npm/rules/tool-surface
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,6 @@
1
+ {
2
+ "auto": {
3
+ "predicate": "depInAnyPackageJson",
4
+ "arg": ["vue", "react", "svelte", "@angular/core", "preact", "solid-js", "@tauri-apps/api", "@capacitor/core"]
5
+ }
6
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ description: Tool Surface — будь-яка дія фронтенду має бути виконуваною без UI (CLI + LLM) через спільний каталог тулів; UI/оркестратор/LLM — рівноправні адаптери
3
+ alwaysApply: true
4
+ version: '1.0'
5
+ ---
6
+
7
+ # Tool Surface — паритет «UI ↔ LLM ↔ оркестратор»
8
+
9
+ ## Принцип
10
+
11
+ **Будь-яка дія, яку людина виконує через фронтенд, мусить бути виконуваною як `tool` — без UI — бо вона організована як іменований виклик зі схемою, до якого однаково дотягуються UI, скриптовий оркестратор і LLM.**
12
+
13
+ Ключовий новий споживач — **LLM**, тож одиниця називається `tool`. Фронтенд — лише один з адаптерів, а не єдині двері.
14
+
15
+ ## Це НЕ про «винести логіку на бекенд»
16
+
17
+ Лінія поділу не «фронт ↔ бек», а:
18
+
19
+ > виклик, досяжний **лише через UI-взаємодію** → погано
20
+ > виклик, досяжний як **іменований tool зі схемою** → добре
21
+
22
+ JS-логіка може лишатися на фронті — її лише треба **витягнути** з обробника події в окремий tool (ім'я + параметри + схема), який однаково кличе UI, оркестратор і LLM. Tool Surface — це **call surface**, не обов'язково бекенд.
23
+
24
+ ## Три шари
25
+
26
+ 1. **Tool Catalog** (`src/tool/`) — декларативний каталог. Кожен tool: `name`, `summary`, схема входу/виходу, `handler`. Handler **може бути фронтендовою JS-функцією** (або делегувати в native/HTTP/DB). Це **єдине джерело правди**; з нього генеруються schema-маніфест (для LLM) і типи клієнта.
27
+ 2. **Dispatch** — одна функція `dispatch(name, input) → { ok, output } | { ok: false, error }`: валідує вхід за схемою, кличе handler, повертає **уніфікований конверт**. Не прив'язана до UI-рендеру/подій.
28
+ 3. **Споживачі** (усі обов'язкові):
29
+ - **UI** — компонент кличе `dispatch`, без inline-логіки дії.
30
+ - **Оркестратор** — машинний вхід `<bin> <tool> '<json>'` (+ `schema`/`list`). Кличуть скрипти, не люди — людський CLI з verb-ами не потрібен.
31
+ - **LLM** — tool-маніфест із каталогу; tool_call маршрутизується в `dispatch`.
32
+
33
+ ## Інваріант паритету (серце правила)
34
+
35
+ - **Жодної дії, досяжної лише через UI-взаємодію.** Обробник події **делегує** в `dispatch`/каталог, а не містить inline-логіку (мережа, мутація моделі, виклик бекенду).
36
+ - Дозволено: логіка у фронтенд-коді. Заборонено: логіка **зашита** в обробник кліку/сабміту так, що дотягтись можна лише кліком.
37
+ - Кожен tool каталогу має **усіх** споживачів (UI + оркестратор + LLM), інакше це не headless.
38
+
39
+ ## Єдине джерело схем
40
+
41
+ Схема входу tool-а живе в каталозі (zod / JSON Schema; для Rust — `schemars`). З неї генеруються: tool-маніфест для LLM (формат залежить від провайдера — OpenAI function-calling, Anthropic tools або MCP), CLI-довідка, типи клієнта. Тул-визначення **не повинні розходитися** з каталогом — лише деривація, не дублювання.
42
+
43
+ ## Уніфікований конверт
44
+
45
+ ```jsonc
46
+ { "ok": true, "output": { /* результат */ } }
47
+ { "ok": false, "error": { "code": "validation|not_found|io|…", "message": "…" } }
48
+ ```
49
+
50
+ Оркестратор: `ok:false` → ненульовий exit. UI/LLM: `Result` / tool_result з `is_error`.
51
+
52
+ ## Дворівнева структура (архітектура спільна, реалізація per-stack)
53
+
54
+ Архітектура **спільна**, реалізація **навмисно розходиться** за стеком. Це правило тримає платформо-незалежне **ядро**; конкретику делегують профільні правила:
55
+
56
+ | Рівень | Що тут | Де |
57
+ |---|---|---|
58
+ | **Ядро** | принцип, інваріант паритету, контракт каталог/`dispatch`/схема/конверт, 3 споживачі | це правило |
59
+ | **Tauri+Rust** | handler делегує в Rust-крейт/бінарник; машинний bin; `invoke`/spawn | `n-tauri` |
60
+ | **Capacitor / pure-web** | handler — JS напряму; bin = node/bun-скрипт, що імпортує handlers | `n-capacitor` |
61
+ | **UI-адаптер** | компонент делегує в `dispatch`, нуль inline-логіки | `n-vue` |
62
+
63
+ ## Конвенція файлів
64
+
65
+ ```
66
+ src/tool/
67
+ catalog.(js|ts) ← каталог тулів (single source): name, summary, input schema, mapping
68
+ dispatch.(js|ts) ← dispatch(name, input) + валідація + конверт
69
+ manifest.(js|ts) ← каталог → LLM tools ; CLI help
70
+ transports.(js|ts) ← UI-транспорт (invoke/fetch) ; CLI-транспорт (spawn/import)
71
+ bin/<app>.mjs ← машинний вхід: <tool> '<json>' | schema | list
72
+ ```
@@ -0,0 +1,82 @@
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
+ ```
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/vue
4
+ resource: plugins/lang-js/rules/vue/
5
+ ---
6
+
7
+ # npm/rules/vue
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/vue/lib
4
+ resource: plugins/lang-js/rules/vue/lib/
5
+ ---
6
+
7
+ # npm/rules/vue/lib
8
+
9
+ | Файл | Тип |
10
+ | ----------------------------------------------------- | --------- |
11
+ | [vue-forbidden-imports.mjs](vue-forbidden-imports.md) | JS Module |
@@ -0,0 +1,265 @@
1
+ ---
2
+ type: JS Module
3
+ title: vue-forbidden-imports.mjs
4
+ resource: plugins/lang-js/rules/vue/lib/vue-forbidden-imports.mjs
5
+ docgen:
6
+ crc: aaa72840
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-блоки, інакше — увесь вміст. Разом з `extractVueScriptBlocks` реалізовані в ядрі (`@7n/rules/scripts/lib/js-source-signals.mjs`, спільні з auto-rules) — тут ре-експорт. |
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))`.