@7n/rules 1.7.0 → 1.7.2
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.
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/rules/abie/http_route_base/http_route_base.mdc +23 -1
- package/rules/adr/madr_format/concern.json +1 -0
- package/rules/adr/madr_format/madr_format.mdc +119 -0
- package/rules/bun/bunfig/bunfig.mdc +5 -0
- package/rules/bun/lint-surface/concern.json +3 -0
- package/rules/bun/lint-surface/lint-surface.mdc +13 -0
- package/rules/capacitor/platforms/docs/main.md +1 -1
- package/rules/capacitor/platforms/main.mjs +5 -1
- package/rules/capacitor/platforms/platforms.mdc +108 -0
- package/rules/changelog/consistency/comparison-models.mdc +46 -0
- package/rules/changelog/consistency/consistency.mdc +35 -0
- package/rules/docker/main.mdc +235 -2
- package/rules/image-avif/avif_generation/avif_generation.mdc +14 -0
- package/rules/js/check/check.mdc +26 -0
- package/rules/js/file-extensions/concern.json +3 -0
- package/rules/js/file-extensions/file-extensions.mdc +12 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +28 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/utils_imports/utils_imports.mdc +15 -0
- package/rules/js-bun-db/connection/concern.json +3 -0
- package/rules/js-bun-db/connection/connection.mdc +42 -0
- package/rules/js-bun-db/package_json/package_json.mdc +15 -1
- package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
- package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
- package/rules/js-bun-db/safety/safety.mdc +458 -0
- package/rules/js-mssql/main.mdc +130 -0
- package/rules/js-mssql/mssql-tvp/concern.json +3 -0
- package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
- package/rules/js-run/configmap/configmap.mdc +6 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +23 -0
- package/rules/js-run/package_json/package_json.mdc +6 -0
- package/rules/js-run/project-structure/concern.json +3 -0
- package/rules/js-run/project-structure/project-structure.mdc +11 -0
- package/rules/js-run/runtime/runtime.mdc +170 -0
- package/rules/js-run/scope/concern.json +3 -0
- package/rules/js-run/scope/scope.mdc +11 -0
- package/rules/k8s/hasura_configmap/hasura_configmap.mdc +6 -0
- package/rules/k8s/hpa_pdb/hpa_pdb.mdc +134 -0
- package/rules/k8s/kubeconform/kubeconform.mdc +38 -0
- package/rules/k8s/kustomization/kustomization.mdc +73 -0
- package/rules/k8s/main.mdc +68 -0
- package/rules/k8s/manifest/manifest.mdc +37 -0
- package/rules/k8s/manifests/docs/fix-manifests.md +3 -1
- package/rules/k8s/manifests/fix-manifests.mjs +11 -0
- package/rules/k8s/manifests/main.mjs +28 -0
- package/rules/k8s/network_policy/network_policy.mdc +33 -0
- package/rules/nginx-default-tpl/http-route/concern.json +1 -0
- package/rules/nginx-default-tpl/http-route/http-route.mdc +54 -0
- package/rules/nginx-default-tpl/template/template.mdc +152 -0
- package/rules/php/tooling/tooling.mdc +7 -6
- package/rules/python/pyproject_toml/pyproject_toml.mdc +17 -1
- package/rules/python/tooling/tooling.mdc +9 -10
- package/rules/rego/main.mdc +14 -0
- package/rules/rust/check/check.mdc +16 -0
- package/rules/style/admin_table/admin_table.mdc +88 -0
- package/rules/style/admin_table/concern.json +7 -0
- package/rules/style/admin_table/docs/index.md +9 -0
- package/rules/style/admin_table/docs/main.md +14 -0
- package/rules/style/admin_table/main.mjs +46 -0
- package/rules/style/colors/colors.mdc +21 -0
- package/rules/style/colors/concern.json +3 -0
- package/rules/style/gap/concern.json +7 -0
- package/rules/style/gap/docs/index.md +9 -0
- package/rules/style/gap/docs/main.md +15 -0
- package/rules/style/gap/gap.mdc +22 -0
- package/rules/style/gap/main.mjs +51 -0
- package/rules/style/quasar/concern.json +3 -0
- package/rules/style/quasar/quasar.mdc +7 -0
- package/rules/style/quasar_fixes/concern.json +7 -0
- package/rules/style/quasar_fixes/docs/index.md +9 -0
- package/rules/style/quasar_fixes/docs/main.md +16 -0
- package/rules/style/quasar_fixes/main.mjs +57 -0
- package/rules/style/quasar_fixes/quasar_fixes.mdc +32 -0
- package/rules/tauri/release/release.mdc +5 -2
- package/rules/tauri/tool_surface/concern.json +16 -0
- package/rules/tauri/tool_surface/docs/index.md +9 -0
- package/rules/tauri/tool_surface/docs/main.md +24 -0
- package/rules/tauri/tool_surface/main.mjs +145 -0
- package/rules/tauri/tool_surface/tool_surface.mdc +29 -0
- package/rules/test/vitest-api-conventions/concern.json +7 -0
- package/rules/test/vitest-api-conventions/docs/index.md +9 -0
- package/rules/test/vitest-api-conventions/docs/main.md +40 -0
- package/rules/test/vitest-api-conventions/main.mjs +186 -0
- package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
- package/rules/text/cspell/cspell.mdc +18 -0
- package/rules/text/markdownlint/markdownlint.mdc +4 -0
- package/rules/text/run-dotenv-linter/run-dotenv-linter.mdc +17 -0
- package/rules/text/run-shellcheck/run-shellcheck.mdc +17 -0
- package/rules/text/run-v8r/run-v8r.mdc +23 -0
- package/rules/vue/composition-api/composition-api.mdc +82 -0
- package/rules/vue/composition-api/concern.json +3 -0
- package/rules/vue/main.mdc +1 -1
- package/rules/vue/nheader-layout/concern.json +3 -0
- package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
- package/rules/vue/packages/packages.mdc +56 -0
- package/rules/vue/quasar-ui/concern.json +3 -0
- package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
- package/rules/vue/structure/concern.json +3 -0
- package/rules/vue/structure/structure.mdc +101 -0
- package/rules/vue/testing/concern.json +3 -0
- package/rules/vue/testing/testing.mdc +40 -0
- package/rules/vue/tfm-translations/concern.json +7 -0
- package/rules/vue/tfm-translations/docs/main.md +29 -0
- package/rules/vue/tfm-translations/main.mjs +55 -0
- package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
- package/rules/vue/vite-config/concern.json +3 -0
- package/rules/vue/vite-config/vite-config.mdc +153 -0
- package/rules/vue/vite-env/concern.json +3 -0
- package/rules/vue/vite-env/vite-env.mdc +61 -0
|
@@ -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
|
+
```
|
package/rules/vue/main.mdc
CHANGED
|
@@ -15,4 +15,4 @@ Rego-перевірки (запускаються через `npx @7n/rules fix
|
|
|
15
15
|
|
|
16
16
|
## Перевірка
|
|
17
17
|
|
|
18
|
-
`npx @7n/rules fix vue` — перевіряє залежності, `vite.config`, наявність **`src/vite-env.d.ts`** з `/// <reference types="vite/client" />` та **`jsconfig.json`** у корені Vue-пакета; обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім'я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/rules/vue/
|
|
18
|
+
`npx @7n/rules fix vue` — перевіряє залежності, `vite.config`, наявність **`src/vite-env.d.ts`** з `/// <reference types="vite/client" />` та **`jsconfig.json`** у корені Vue-пакета; обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім'я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/rules/vue/lib/vue-forbidden-imports.mjs`).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
## NHeader — телепорт-слоти на сторінках
|
|
2
|
+
|
|
3
|
+
У проектах з `NHeader` використовуй `<teleport>` для вбудовування контенту сторінки в шапку:
|
|
4
|
+
|
|
5
|
+
| Слот | Призначення |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `#header-subtitle` | Заголовок сторінки (якщо потрібно перевизначити subtitle) |
|
|
8
|
+
| `#header-center` | Важливі повідомлення та елементи управління |
|
|
9
|
+
| `#header-filters` | Фільтри, кнопки та інші елементи управління сторінки |
|
|
10
|
+
|
|
11
|
+
Оскільки `NHeader` за замовчуванням темний, додавай до полів і селектів у телепортах: `dark`, `standout="bg-white text-primary"`, `:options-dark="false"`.
|
|
12
|
+
|
|
13
|
+
Завжди обгортай `<teleport>` в `v-if="mounted"` (де `mounted` — `$ref(false)`, що встановлюється в `onMounted`), щоб уникнути помилки відсутності target-елемента при SSR / першому рендері.
|
|
14
|
+
|
|
15
|
+
```vue
|
|
16
|
+
<template>
|
|
17
|
+
<q-page>
|
|
18
|
+
<!-- ФІЛЬТРИ В ШАПЦІ -->
|
|
19
|
+
<teleport v-if="mounted" to="#header-filters">
|
|
20
|
+
<div class="col row items-center n-gap-sm q-pa-sm">
|
|
21
|
+
<q-input
|
|
22
|
+
v-model="pageStore.filterName"
|
|
23
|
+
:label="t`Поиск по названию`"
|
|
24
|
+
debounce="200"
|
|
25
|
+
standout="bg-white text-primary"
|
|
26
|
+
clearable
|
|
27
|
+
dense
|
|
28
|
+
dark
|
|
29
|
+
class="col"
|
|
30
|
+
style="min-width: 120px">
|
|
31
|
+
<template #prepend>
|
|
32
|
+
<q-icon name="search" />
|
|
33
|
+
</template>
|
|
34
|
+
</q-input>
|
|
35
|
+
|
|
36
|
+
<n-select-multi
|
|
37
|
+
v-model="pageStore.filterRequestTypes"
|
|
38
|
+
:options="requestTypeOptions"
|
|
39
|
+
:label="t`Тип запроса`"
|
|
40
|
+
dark
|
|
41
|
+
standout="bg-white text-primary"
|
|
42
|
+
:options-dark="false"
|
|
43
|
+
style="min-width: 180px; max-width: 400px"
|
|
44
|
+
dense
|
|
45
|
+
emit-value
|
|
46
|
+
map-options
|
|
47
|
+
clearable
|
|
48
|
+
searchable
|
|
49
|
+
:stack-label="false" />
|
|
50
|
+
|
|
51
|
+
<q-btn
|
|
52
|
+
@click="addItem"
|
|
53
|
+
icon="add"
|
|
54
|
+
:label="t`Добавить`"
|
|
55
|
+
color="primary"
|
|
56
|
+
no-caps
|
|
57
|
+
padding="8px 12px"
|
|
58
|
+
unelevated />
|
|
59
|
+
</div>
|
|
60
|
+
</teleport>
|
|
61
|
+
|
|
62
|
+
<!-- ОСНОВНИЙ КОНТЕНТ -->
|
|
63
|
+
...
|
|
64
|
+
</q-page>
|
|
65
|
+
</template>
|
|
66
|
+
<script setup>
|
|
67
|
+
const mounted = $ref(false)
|
|
68
|
+
onMounted(() => { mounted = true })
|
|
69
|
+
</script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Layout з NHeader
|
|
73
|
+
|
|
74
|
+
Для нових layout-ів використовуй `NHeader` з вбудованими `NLang` і `NMenu`.
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<template>
|
|
78
|
+
<q-layout view="hHh Lpr lFf">
|
|
79
|
+
<n-header
|
|
80
|
+
v-model="leftSideOpened"
|
|
81
|
+
:logo="baseUrl + 'logo.png'"
|
|
82
|
+
:logo-url="homeUrl"
|
|
83
|
+
title="My App"
|
|
84
|
+
:subtitle="subtitle"
|
|
85
|
+
:username="userName"
|
|
86
|
+
toolbar-dark>
|
|
87
|
+
<template #top-toolbar>
|
|
88
|
+
<div class="platform-ios-only q-py-lg" />
|
|
89
|
+
</template>
|
|
90
|
+
<div>default slot content</div>
|
|
91
|
+
</n-header>
|
|
92
|
+
|
|
93
|
+
<!-- ЛЕВАЯ КОЛОНКА -->
|
|
94
|
+
<q-drawer v-model="leftSideOpened" :breakpoint="700" :width="300" overlay class="col column shadow-5 bg-grey-1">
|
|
95
|
+
<div class="platform-ios-only q-py-lg" />
|
|
96
|
+
<div v-if="!$q.screen.gt.xs" class="q-pa-sm row items-center">
|
|
97
|
+
<q-icon name="account_circle" color="primary" size="32px" class="q-mr-sm" />
|
|
98
|
+
<div class="text-subtitle1">{{ user.name }}</div>
|
|
99
|
+
</div>
|
|
100
|
+
<n-menu v-model="activeMenu" :menu="menu" />
|
|
101
|
+
<q-space />
|
|
102
|
+
<n-menu :menu="homeMenu" />
|
|
103
|
+
<div class="platform-ios-only q-py-md" />
|
|
104
|
+
</q-drawer>
|
|
105
|
+
|
|
106
|
+
<q-page-container>
|
|
107
|
+
<router-view />
|
|
108
|
+
</q-page-container>
|
|
109
|
+
</q-layout>
|
|
110
|
+
</template>
|
|
111
|
+
<script setup>
|
|
112
|
+
import { lang, tf as tfm } from '@nitra/tfm'
|
|
113
|
+
const t = tfm.bind({ tr: getTr() })
|
|
114
|
+
|
|
115
|
+
const baseUrl = import.meta.env.BASE_URL
|
|
116
|
+
const homeUrl = String.raw`https:\\` + import.meta.env.VITE_DOMAIN
|
|
117
|
+
|
|
118
|
+
// Ліва колонка
|
|
119
|
+
const leftSideOpened = $ref(false)
|
|
120
|
+
const activeMenu = $ref(null)
|
|
121
|
+
|
|
122
|
+
// Заголовок у шапці з поточного пункту меню
|
|
123
|
+
const subtitle = computed(() => (activeMenu ? getTr()[activeMenu.labelKey]?.[lang.value] || activeMenu?.labelKey : ''))
|
|
124
|
+
|
|
125
|
+
// Меню
|
|
126
|
+
const menu = computed(() =>
|
|
127
|
+
[
|
|
128
|
+
{
|
|
129
|
+
icon: 'sym_o_store',
|
|
130
|
+
label: t`Клиенты`,
|
|
131
|
+
labelKey: t`Клиенты`,
|
|
132
|
+
routeName: 'customer'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
icon: 'sym_o_route',
|
|
136
|
+
label: t`Маршруты и визиты`,
|
|
137
|
+
items: [
|
|
138
|
+
{
|
|
139
|
+
icon: 'sym_o_route',
|
|
140
|
+
label: t`Маршруты`,
|
|
141
|
+
labelKey: t`Маршруты`,
|
|
142
|
+
routeName: 'route'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
icon: 'sym_o_event_upcoming',
|
|
146
|
+
label: t`Переносы маршрутов`,
|
|
147
|
+
labelKey: t`Переносы маршрутов`,
|
|
148
|
+
routeName: 'route_postpone'
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
].filter(item => {
|
|
153
|
+
if (item.items?.length) {
|
|
154
|
+
item.items = item.items.filter(i => can[i.permissionRoute || i.routeName])
|
|
155
|
+
return item.items.length > 0
|
|
156
|
+
}
|
|
157
|
+
return can[item.routeName]
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* LOCALIZATION
|
|
163
|
+
* @returns {object} translations
|
|
164
|
+
*/
|
|
165
|
+
function getTr() {
|
|
166
|
+
return {
|
|
167
|
+
Клиенты: { en: 'Customers', ro: 'Clienți', tr: 'Müşteriler' }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
</script>
|
|
171
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## Заборонені імпорти у Vue-пакетах: `vue` value-імпорти та Node-нативні модулі у `.vue`
|
|
2
|
+
|
|
3
|
+
Concern `packages` перевіряє залежності, `vite.config`, `vite-env.d.ts`/`jsconfig.json`, IDE-рекомендації та джерела кожного workspace-пакета з `vue` у `dependencies` (`npm/rules/vue/packages/main.mjs`, повністю описано в `npm/rules/vue/main.mdc`). Цей файл документує детально дві з цих перевірок — заборону value-імпортів з `vue` і заборону імпортів Node-нативних модулів у `.vue` SFC — обидві реалізовані через **oxc-parser** у `npm/rules/vue/lib/vue-forbidden-imports.mjs`.
|
|
4
|
+
|
|
5
|
+
### Заборона явних value-імпортів з `vue`
|
|
6
|
+
|
|
7
|
+
Потрібно використовувати `unplugin-auto-import` для автоматичного імпортування Vue API. Коли `'vue'` додано до `AutoImport.imports` у `vite.config`, явні value-імпорти вигляду `import { ref, computed } from 'vue'` у файлах додатка **заборонені** — API Vue підставляється автоматично.
|
|
8
|
+
|
|
9
|
+
Дозволені лише:
|
|
10
|
+
|
|
11
|
+
- side-effect: `import 'vue'`
|
|
12
|
+
- type-only: `import type { … } from 'vue'` або `import { type Foo } from 'vue'`
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<!-- ❌ заборонено (auto-import покриває) -->
|
|
16
|
+
<script setup>
|
|
17
|
+
import { ref, computed, watch, onMounted } from 'vue'
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- ✅ дозволено — не потрібен ручний імпорт -->
|
|
21
|
+
<script setup>
|
|
22
|
+
const count = $ref(0)
|
|
23
|
+
const doubled = $computed(() => count * 2)
|
|
24
|
+
onMounted(() => { /* ... */ })
|
|
25
|
+
</script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Виняток:** бібліотеки компонентів (`vue` у `peerDependencies`) — їхні джерела не проходять через `unplugin-auto-import` споживача, тому явні `import { ref } from 'vue'` там обовʼязкові. Визначається через `isVueComponentLibraryPkg` у `npm/rules/vue/packages/main.mjs`. Якщо `vite.config` ще не налаштовано на `'vue'` у `AutoImport.imports` (не бібліотека компонентів) — перевірка value-імпортів теж пропускається, доки не додано `'vue'` до `AutoImport.imports` (інакше видалення імпортів зламає код).
|
|
29
|
+
|
|
30
|
+
Перевірка виконується через **oxc-parser** (`module.staticImports`) у `npm/rules/vue/lib/vue-forbidden-imports.mjs`, функція `findForbiddenVueImportsInSourceFile`. Для `.vue` — лише вміст `<script>` тегів, `<template>` ігнорується.
|
|
31
|
+
|
|
32
|
+
### Заборонено імпортувати Node-нативні модулі у `.vue` SFC
|
|
33
|
+
|
|
34
|
+
Vue SFC виконується у браузері, тож API Node.js там недоступне. У `<script>` (включно з `<script setup>`)
|
|
35
|
+
заборонено будь-які імпорти вбудованих модулів Node — як з префіксом `node:`, так і bare-ім'ям модуля
|
|
36
|
+
(включно з підшляхами):
|
|
37
|
+
|
|
38
|
+
```vue title="погано — ламає білд"
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
41
|
+
import fs from 'fs'
|
|
42
|
+
import { readFile } from 'fs/promises'
|
|
43
|
+
import path from 'node:path'
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Якщо потрібна логіка з Node API — винеси її у server-side утіліту (наприклад, у backend-пакет монорепо)
|
|
48
|
+
та звертайся до неї через HTTP/GraphQL. Браузерні замінники (`window.crypto`, `URL`, `setTimeout` глобальний,
|
|
49
|
+
`AbortController` тощо) використовуй напряму, без import.
|
|
50
|
+
|
|
51
|
+
Правило стосується саме `.vue` файлів. Допоміжні `.ts`/`.js` модулі, які споживаються лише server-side
|
|
52
|
+
(наприклад, окремий пакет утіліт), можуть імпортувати Node-built-ins без обмежень.
|
|
53
|
+
|
|
54
|
+
Перевірка реалізована у `npm/rules/vue/lib/vue-forbidden-imports.mjs` через функцію
|
|
55
|
+
`findForbiddenNodeImportsInVueFile` — аналізує лише `<script>` блоки SFC через **oxc-parser**,
|
|
56
|
+
ігноруючи `<template>`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Quasar як UI-основа
|
|
2
|
+
|
|
3
|
+
У Vue-проектах використовуй **Quasar** як базовий UI-фреймворк:
|
|
4
|
+
|
|
5
|
+
- **Компоненти:** `q-btn`, `q-input`, `q-select`, `q-table`, `q-dialog`, `q-card`, `q-layout`, `q-page`, `q-drawer` тощо — основа UI.
|
|
6
|
+
- **Плагіни:** `Notify`, `Dialog`, `Loading` та інші Quasar-плагіни.
|
|
7
|
+
- **Кольори:** використовуй Quasar CSS-змінні (`primary`, `secondary`, `accent`, `positive`, `negative`, `warning`, `info`, `dark`) і утиліти (`text-primary`, `bg-accent` тощо).
|
|
8
|
+
- **Утиліти:** flex-layout (`row`, `col`, `items-center`), spacing (`q-pa-md`, `q-mt-sm`), shadow (`shadow-2`) — зі стандартної бібліотеки Quasar.
|
|
9
|
+
- **Кастомні компоненти** `@nitra/components` — **надбудова** над Quasar, а не заміна; їх слід надавати перевагу лише там, де вони є.
|
|
10
|
+
|
|
11
|
+
## @nitra/components — надавай перевагу перед Quasar-компонентами
|
|
12
|
+
|
|
13
|
+
При створенні нової функціональності використовуй компоненти `@nitra/components`, якщо логіка компонента дозволяє отримати потрібний функціонал. Заміни:
|
|
14
|
+
|
|
15
|
+
| Завдання | `@nitra/components` | Замість Quasar |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Діалоги | `NDialog` | `q-dialog` |
|
|
18
|
+
| Multi-вибір | `NSelectMulti` | `q-select` (multiple) |
|
|
19
|
+
| Текстовий редактор | `NEditor` | `q-editor` |
|
|
20
|
+
| Вибір дати | `NDate` | — |
|
|
21
|
+
| Вибір місяця/року | `NDateMonthYear` | — |
|
|
22
|
+
| Діапазон дат | `NDateRange` | — |
|
|
23
|
+
| Дата і час | `NDateTime` | — |
|
|
24
|
+
| Drag&drop список | `NDraggableList` | — |
|
|
25
|
+
| Редаговане значення | `NEditableString` | — |
|
|
26
|
+
| Повідомлення | `NCallout` | — |
|
|
27
|
+
| Хедер проекту | `NHeader` | — |
|
|
28
|
+
| Перемикання мов | `NLang` | — (якщо `NHeader` не використовується) |
|
|
29
|
+
| Меню проекту | `NMenu` | — |
|
|
30
|
+
| Зображення (масив / одиночне) | `NImages` | — |
|
|
31
|
+
| Завантаження файлів | `NUploader` | — |
|
|
32
|
+
| Вибір колонок `q-table` | `NTableColumns` | — |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
## Структура папок
|
|
2
|
+
|
|
3
|
+
```javascript
|
|
4
|
+
const folderStructure = `
|
|
5
|
+
src/
|
|
6
|
+
components/
|
|
7
|
+
composables/
|
|
8
|
+
views/
|
|
9
|
+
router/
|
|
10
|
+
store/
|
|
11
|
+
assets/
|
|
12
|
+
public/
|
|
13
|
+
App.vue
|
|
14
|
+
main.mjs
|
|
15
|
+
`
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Найменування файлів
|
|
19
|
+
|
|
20
|
+
- **SFC:** імена файлів компонентів у **PascalCase** починаючи з букви N(`NMyWidget.vue`).
|
|
21
|
+
- **Інші JS-модулі:** узгоджено **kebab-case** (`date-utils.mjs`).
|
|
22
|
+
|
|
23
|
+
### Модулі та архітектура
|
|
24
|
+
|
|
25
|
+
- **ES modules** (`import`/`export`), один модуль — одна відповідальність, уникай **circular dependencies**.
|
|
26
|
+
- **Composition** замість успадкування; логіку для повторного використання винось у **composables** (замість зайвих HOC, де це доречно).
|
|
27
|
+
|
|
28
|
+
### Code splitting
|
|
29
|
+
|
|
30
|
+
- **Route-based:** lazy-імпорти в маршрутах (`() => import('...')`) і async components.
|
|
31
|
+
|
|
32
|
+
## Додаткові вказівки
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const additionalInstructions = `
|
|
36
|
+
1. Використовуй JavaScript
|
|
37
|
+
2. Коректно оголошуй props, emits, defineModel
|
|
38
|
+
3. За потреби використовуй компонент Teleport у Vue 3
|
|
39
|
+
4. Застосовуй Suspense для async components
|
|
40
|
+
5. Реалізуй належний error handling
|
|
41
|
+
6. Дотримуйся Vue 3 style guide і naming conventions
|
|
42
|
+
7. Використовуй Vite для швидкої розробки та збірки
|
|
43
|
+
`
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Детальні вказівки
|
|
47
|
+
|
|
48
|
+
1. **Vue router**: Завжди використовуй Vue router для маршрутизації в проекті версії 5 та його file-based routing.
|
|
49
|
+
2. **Компонент Teleport**: Використовуй Teleport у Vue 3, коли потрібно рендерити поза поточною ієрархією DOM.
|
|
50
|
+
3. **Suspense для async components**: Застосовуй Suspense для асинхронних компонентів і кращого UX.
|
|
51
|
+
4. **Error handling**: Реалізуй глобальний error handling механізмами Vue 3, щоб коректно перехоплювати та обробляти помилки.
|
|
52
|
+
5. **Style guide і naming**: Дотримуйся офіційного Vue 3 style guide і naming conventions для узгодженості кодової бази.
|
|
53
|
+
6. **Vue macros**: Використовуй Vue macros для більш ефективного розробки компонентів.
|
|
54
|
+
7. **Vue Reactivity Transform**: Використовуй Vue Reactivity Transform для змінних в компонентах.
|
|
55
|
+
|
|
56
|
+
## Коментарі в `<template>`
|
|
57
|
+
|
|
58
|
+
Додавай коментарі в `<template>` відповідно до логічного призначення блоку. Коментарі допомагають швидко орієнтуватися в розмітці.
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<template>
|
|
62
|
+
<q-page>
|
|
63
|
+
<!-- ФІЛЬТРИ В ШАПЦІ -->
|
|
64
|
+
<teleport v-if="mounted" to="#header-filters">...</teleport>
|
|
65
|
+
|
|
66
|
+
<!-- ТАБЛИЦЯ -->
|
|
67
|
+
<q-table ... />
|
|
68
|
+
|
|
69
|
+
<!-- ДІАЛОГ РЕДАГУВАННЯ -->
|
|
70
|
+
<n-dialog v-model="editDialog">...</n-dialog>
|
|
71
|
+
</q-page>
|
|
72
|
+
</template>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Pinia store для стану сторінки
|
|
76
|
+
|
|
77
|
+
Зберігай у Pinia store:
|
|
78
|
+
|
|
79
|
+
- вибрані значення фільтрів
|
|
80
|
+
- вибрані для відображення колонки (`NTableColumns`)
|
|
81
|
+
- кількість записів на сторінці (pagination)
|
|
82
|
+
|
|
83
|
+
Називай store за назвою сторінки або компонента — `customerPageStore`, `routePageStore` тощо. На сторінці звертайся до нього через змінну `pageStore`.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// store/customerPage.mjs
|
|
87
|
+
export const useCustomerPageStore = defineStore('customerPage', {
|
|
88
|
+
state: () => ({
|
|
89
|
+
filterName: '',
|
|
90
|
+
filterStatus: [],
|
|
91
|
+
columns: [],
|
|
92
|
+
rowsPerPage: 20
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<script setup>
|
|
99
|
+
const pageStore = useCustomerPageStore()
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## Тестування Vue-компонентів
|
|
2
|
+
|
|
3
|
+
### Unit + Component / DOM
|
|
4
|
+
|
|
5
|
+
**Vitest** (`vitest`) + **Vue Test Utils** з **happy-dom** як DOM-середовищем. Це канон, узгоджений з `test.mdc` (Stryker з vitest-runner + `perTest`-аналіз покриття). `vitest.config.mjs` повторно використовує `vite.config.js` через `mergeConfig` і перемикає `environment` на `'happy-dom'`:
|
|
6
|
+
|
|
7
|
+
```js title="vitest.config.mjs"
|
|
8
|
+
import { defineConfig, mergeConfig } from 'vitest/config'
|
|
9
|
+
import viteConfig from './vite.config.js'
|
|
10
|
+
|
|
11
|
+
export default mergeConfig(viteConfig, defineConfig({
|
|
12
|
+
test: {
|
|
13
|
+
include: ['**/*.test.{js,mjs}', 'tests/**/*.test.{js,mjs}'],
|
|
14
|
+
environment: 'happy-dom',
|
|
15
|
+
coverage: { provider: 'v8', reporter: ['lcov', 'text-summary'] }
|
|
16
|
+
}
|
|
17
|
+
}))
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`jsdom` не використовуй — happy-dom швидший і достатній для типових Vue-компонентних тестів.
|
|
21
|
+
|
|
22
|
+
У `package.json#scripts` тримай `"test": "vitest run"`; vitest devDeps (`vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`) — у кореневому `devDependencies` (npm-module rule забороняє devDeps у published workspace-у).
|
|
23
|
+
|
|
24
|
+
### Чому цей файл — docs-only
|
|
25
|
+
|
|
26
|
+
Вимога `scripts.test === "vitest run"` і `environment: 'happy-dom'` у `vitest.config` стосується Vue-пакетів, які **вже мають** компонентні тести. Не кожен workspace-пакет з `vue` у `dependencies` зобов'язаний мати компонентні тести на цьому етапі (наприклад, sandbox/demo-пакет для ручного прогону Vite-конфігурації без жодного `*.test.*` файлу) — механічна перевірка `scripts.test`/`vitest.config` для **усіх** таких пакетів дає хибні спрацювання там, де тестів свідомо ще немає. Тому концерн лишається docs-only.
|
|
27
|
+
|
|
28
|
+
Суміжні вимоги вже перевіряються іншими concern-ами: кореневий `devDependencies` з `vitest`/`@vitest/coverage-v8`/`@stryker-mutator/vitest-runner` — концерн `packages` (`checkRootVitestDevDeps`); заборона `jsdom`/`vitest` у самому Vue-пакеті — Rego-концерн `package_json`.
|
|
29
|
+
|
|
30
|
+
### E2E
|
|
31
|
+
|
|
32
|
+
**Playwright** — змістовні сценарії користувацьких потоків.
|
|
33
|
+
|
|
34
|
+
Проекту повинен бути покритий тестами E2E за допомогою Playwright.
|
|
35
|
+
|
|
36
|
+
Вимога E2E-покриття Playwright не перевіряється механічно (немає детермінованого способу оцінити «повноту» покриття користувацьких сценаріїв) — залишається на розсуд ревʼю.
|
|
37
|
+
|
|
38
|
+
### CI/CD
|
|
39
|
+
|
|
40
|
+
У pipeline: **install**, **lint**, **test**, **vite build**; артефакти з продакшен-режиму.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/vue/tfm-translations/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 9c5fcd2b
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Модуль перевіряє конвенцію `@nitra/tfm` з `vue.mdc` (розділ tfm-translations): якщо `.vue`-файл іменовано імпортує `tf` з `@nitra/tfm`, у тому ж файлі має бути оголошена функція `getTr`, що повертає обʼєкт перекладів.
|
|
12
|
+
|
|
13
|
+
## Поведінка
|
|
14
|
+
|
|
15
|
+
Per-file lint по змінених `.vue`-файлах (`ctx.files`). Для кожного файлу:
|
|
16
|
+
|
|
17
|
+
- шукає іменований імпорт з `@nitra/tfm`, у списку якого є специфікатор `tf` (з опційним `as <alias>`);
|
|
18
|
+
- якщо такий імпорт знайдено — перевіряє наявність у файлі оголошення `getTr` (`function getTr(...)` або `const/let getTr = ...`); відсутність — порушення.
|
|
19
|
+
|
|
20
|
+
Файли без імпорту `tf` з `@nitra/tfm` (включно з імпортом лише інших експортів модуля, напр. `lang`) не перевіряються. Без `ctx.files` (delta відсутня) перевірка нічого не робить.
|
|
21
|
+
|
|
22
|
+
## Публічний API
|
|
23
|
+
|
|
24
|
+
`lint(ctx)` — detector-функція lint-поверхні: приймає контекст лінту (`cwd`, `files`), повертає список порушень.
|
|
25
|
+
|
|
26
|
+
## Гарантії поведінки
|
|
27
|
+
|
|
28
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
29
|
+
- Перевіряє лише файли з розширенням `.vue`; інші файли зі списку `ctx.files` ігноруються.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
3
|
+
|
|
4
|
+
/** Іменований імпорт з `@nitra/tfm` — захоплює список імен усередині `{ ... }`. */
|
|
5
|
+
const TFM_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@nitra\/tfm['"]/
|
|
6
|
+
|
|
7
|
+
/** Один запис іменованого імпорту `tf` (з опційним `as <alias>`). */
|
|
8
|
+
const TF_SPECIFIER_RE = /^tf(?:\s+as\s+\w+)?$/
|
|
9
|
+
|
|
10
|
+
/** Оголошення функції `getTr` — `function getTr(...)` або `const/let getTr = (...)`. */
|
|
11
|
+
const GET_TR_DECL_RE = /(?:function\s+getTr\s*\(|(?:const|let|var)\s+getTr\s*=)/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Чи імпортує вміст файлу `tf` (можливо з `as <alias>`) саме з `@nitra/tfm`.
|
|
15
|
+
* @param {string} content вихідний текст файлу
|
|
16
|
+
* @returns {boolean} `true`, якщо знайдено іменований імпорт `tf` з `@nitra/tfm`
|
|
17
|
+
*/
|
|
18
|
+
function importsTfFromTfm(content) {
|
|
19
|
+
const m = TFM_IMPORT_RE.exec(content)
|
|
20
|
+
if (!m) return false
|
|
21
|
+
return m[1].split(',').some(entry => TF_SPECIFIER_RE.test(entry.trim()))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detector concern-а `tfm-translations`: якщо `.vue`-файл імпортує `tf` з `@nitra/tfm`,
|
|
26
|
+
* у цьому ж файлі має бути оголошена функція `getTr()` з перекладами (vue.mdc tfm-translations).
|
|
27
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту (`ctx.files` — delta-файли).
|
|
28
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат з порушеннями.
|
|
29
|
+
*/
|
|
30
|
+
export async function lint(ctx) {
|
|
31
|
+
const reporter = createViolationReporter(ctx)
|
|
32
|
+
const { fail } = reporter
|
|
33
|
+
|
|
34
|
+
if (ctx.files === undefined || ctx.files.length === 0) return reporter.result()
|
|
35
|
+
|
|
36
|
+
const { readFile } = await import('node:fs/promises')
|
|
37
|
+
const { join } = await import('node:path')
|
|
38
|
+
const { existsSync } = await import('node:fs')
|
|
39
|
+
|
|
40
|
+
for (const file of ctx.files) {
|
|
41
|
+
if (!file.endsWith('.vue')) continue
|
|
42
|
+
const absPath = join(ctx.cwd, file)
|
|
43
|
+
if (!existsSync(absPath)) continue
|
|
44
|
+
const content = await readFile(absPath, 'utf8')
|
|
45
|
+
if (!importsTfFromTfm(content)) continue
|
|
46
|
+
if (GET_TR_DECL_RE.test(content)) continue
|
|
47
|
+
fail(
|
|
48
|
+
`${file}: імпортує 'tf' з '@nitra/tfm', але не оголошує функцію getTr() з перекладами ` +
|
|
49
|
+
`(vue.mdc tfm-translations)`,
|
|
50
|
+
{ file }
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return reporter.result()
|
|
55
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## @nitra/tfm — переклади
|
|
2
|
+
|
|
3
|
+
Використовуй `@nitra/tfm` для всіх текстів. Переклади для всіх мов проекту оголошуй наприкінці `<script setup>` у функції `getTr()`. Змінна `lang` із `@nitra/tfm` — для визначення або зміни поточної мови застосунку.
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<template>
|
|
7
|
+
{{ lang }}
|
|
8
|
+
{{ t`Анкеты` }}
|
|
9
|
+
{{ subtitle }}
|
|
10
|
+
</template>
|
|
11
|
+
<script setup>
|
|
12
|
+
import { lang, tf as tfm } from '@nitra/tfm'
|
|
13
|
+
const t = tfm.bind({ tr: getTr() })
|
|
14
|
+
const subtitle = $computed(() => t`Анкеты`)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* LOCALIZATION
|
|
18
|
+
* @returns {object} translations
|
|
19
|
+
*/
|
|
20
|
+
function getTr() {
|
|
21
|
+
return {
|
|
22
|
+
Анкеты: { en: 'Surveys', ro: 'Sondaje', tr: 'Anketler' }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Перевірка
|
|
29
|
+
|
|
30
|
+
Якщо `.vue`-файл іменовано імпортує `tf` (з опційним `as <alias>`) саме з `@nitra/tfm`, у тому ж файлі має бути оголошена функція `getTr` (`function getTr(...)` або `const/let getTr = ...`). Файли, які не імпортують `tf` з `@nitra/tfm`, не перевіряються.
|
|
31
|
+
|
|
32
|
+
Це лише синтаксична перевірка структури (наявність `getTr` поряд з імпортом `tf`) — вона **не** оцінює повноту чи коректність самих перекладів (усі мови проекту, актуальність рядків тощо), це залишається на розсуд ревʼю.
|