@7n/rules 1.7.0 → 1.7.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.
- package/CHANGELOG.md +8 -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/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,29 @@
|
|
|
1
|
+
## Tool Surface у Tauri (реалізація ядра `tool-surface`)
|
|
2
|
+
|
|
3
|
+
Це per-stack реалізація правила **`tool-surface`** (`n-tool-surface`) для Tauri+Rust. Контракт (каталог → `dispatch` → UI/оркестратор/LLM, інваріант паритету, конверт) — у тому правилі; тут — як він лягає на Tauri.
|
|
4
|
+
|
|
5
|
+
**Реальна робота живе в Rust, JS-каталог — тонкий call surface.** Handler тула не містить логіки сам — він **делегує** в native. Одна реалізація в Rust-крейті backs два споживачі:
|
|
6
|
+
|
|
7
|
+
- **Бінарник** (`src-tauri` як CLI, або окремий crate-bin) — headless-вхід для оркестратора;
|
|
8
|
+
- **Tauri-команда** (`#[tauri::command]`) — той самий крейт-fn, обгорнутий для UI.
|
|
9
|
+
|
|
10
|
+
**Два транспорти одного каталогу** (`src/tool/transports`):
|
|
11
|
+
|
|
12
|
+
- **UI (in-app):** `invoke(tool.tauri, input)` → Tauri-команда → крейт. Ключі `input` мапляться 1:1 на аргументи команди (camelCase, напр. `tasksDir`); поля вкладених struct лишаються snake_case (Tauri конвертить лише імена top-level аргументів).
|
|
13
|
+
- **Оркестратор (headless):** `bin/<app>.mjs` спавнить зібраний бінарник (`<bin> <verb> …` per-verb, або уніфікований `<bin> exec '<json>'`), парсить JSON stdout.
|
|
14
|
+
|
|
15
|
+
**Конверт:** Tauri-команда повертає `Result<T, String>`; адаптер мапить у `{ ok, output }` / `{ ok:false, error }`. Бінарник друкує конверт у stdout, exit ≠ 0 на `ok:false`.
|
|
16
|
+
|
|
17
|
+
**Єдине джерело схем:** надавай перевагу `schemars`-derive на Rust-param-структурах → бінарник віддає маніфест (`<bin> schema`); або тримай схему в JS-каталозі й валідуй до `invoke`. **Не дублюй** контракт між Rust і JS — лише деривація + спільні тест-вектори.
|
|
18
|
+
|
|
19
|
+
**LLM-раннер in-app:** chat-loop ходить до OpenAI-сумісного ендпоінта через `tauri-plugin-http` fetch (бо webview-fetch обмежений CSP/capability), а тули виконує через спільний `dispatch` (той самий, що й UI).
|
|
20
|
+
|
|
21
|
+
### Що перевіряє автоматичний lint (`main.mjs`)
|
|
22
|
+
|
|
23
|
+
Для кожного Tauri-workspace (маркер — `tauri.conf.json` чи `src-tauri/tauri.conf.json`) детектор збирає slugs плагінів, яких торкається JS/TS/Vue-код у `src/**`: через wrapper-import `@tauri-apps/plugin-<slug>` або прямий `invoke('plugin:<slug>|<command>')`. Для кожного знайденого `<slug>` перевіряються три незалежні умови — будь-яка провалена дає окрему violation:
|
|
24
|
+
|
|
25
|
+
1. **`tool-surface-plugin-dep-missing`** — `src-tauri/Cargo.toml` не оголошує crate `tauri-plugin-<slug>` у жодній секції `[dependencies]`/`[target.*.dependencies]`.
|
|
26
|
+
2. **`tool-surface-plugin-not-registered`** — `src-tauri/src/lib.rs` ніде не згадує rust-ідентифікатор плагіна (`tauri-plugin-dialog` → `tauri_plugin_dialog`, дефіси заміняються на підкреслення), тобто `builder` його не реєструє.
|
|
27
|
+
3. **`tool-surface-plugin-capability-missing`** — жоден файл `src-tauri/capabilities/*.json` не дає permission `<slug>` чи `<slug>:*`.
|
|
28
|
+
|
|
29
|
+
Кожна з трьох дір — тихий рантайм-фейл без компіляційної помилки: (1) код не збереться на CI іншого профілю або впаде при оновленні залежностей, (2) `invoke` впаде з "plugin not found", (3) `invoke` впаде permission-denied, видимий лише в `console.error`. Перевірено вживу на проєкті myllm.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/test/vitest-api-conventions/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 4ef8e575
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Детектор concern-а `vitest-api-conventions`: перевіряє, що жоден тестовий файл проєкту
|
|
12
|
+
не викликає `expect(...).toBe(...)` з об'єктним чи масивним літералом як першим аргументом.
|
|
13
|
+
Така перевірка завжди хибна незалежно від вмісту (reference equality на новоствореному
|
|
14
|
+
значенні) — канонічна заміна для об'єктів/масивів — `toEqual` (deep equality).
|
|
15
|
+
|
|
16
|
+
## Поведінка
|
|
17
|
+
|
|
18
|
+
Обходить дерево проєкту (пропускаючи `node_modules` і шляхи з cursor-ignore) і збирає всі
|
|
19
|
+
файли з іменем `*.test.mjs`/`*.test.js`. У кожному файлі шукає виклики `.toBe(`, чий перший
|
|
20
|
+
аргумент — саме об'єктний (`{...}`) чи масивний (`[...]`) літерал: сканує парність дужок
|
|
21
|
+
з урахуванням рядкових і template-літералів усередині, щоб не збитись на дужки в рядках.
|
|
22
|
+
Виклик рахується порушенням лише якщо одразу після закриваючої дужки літерала (з пропуском
|
|
23
|
+
пробілів) іде дужка, що закриває сам виклик, `)` — тобто ланцюжок на кшталт
|
|
24
|
+
`.toBe([...].join('\n'))` не матчиться, бо результат виклику — рядок-примітив, а не
|
|
25
|
+
посилання на масив/обʼєкт.
|
|
26
|
+
|
|
27
|
+
Для кожного знайденого випадку формує порушення з файлом (відносний шлях від cwd) і
|
|
28
|
+
номером рядка.
|
|
29
|
+
|
|
30
|
+
## Публічний API
|
|
31
|
+
|
|
32
|
+
`lint(ctx)` — читає всі тестові файли проєкту (`ctx.cwd`), перевіряє їх на заборонений
|
|
33
|
+
патерн `toBe` з об'єктним/масивним літералом і повертає список порушень
|
|
34
|
+
(`{ file, message, reason }` на кожен знайдений виклик), або порожній результат, якщо
|
|
35
|
+
жоден тестовий файл не порушує конвенцію (test.mdc, vitest-api-conventions).
|
|
36
|
+
|
|
37
|
+
## Гарантії поведінки
|
|
38
|
+
|
|
39
|
+
- Read-only: не виконує операцій запису (ФС/БД) — лише читає файли й повертає порушення.
|
|
40
|
+
- Сканує лише `*.test.mjs`/`*.test.js`; інші файли (в т.ч. `node_modules`) ігноруються.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, relative } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
6
|
+
import { loadCursorIgnorePaths } from '../../../scripts/lib/load-cursor-config.mjs'
|
|
7
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `expect(x).toBe({…})` / `expect(x).toBe([…])` — `toBe` — це `Object.is`
|
|
11
|
+
* (reference equality); нове об'єктне/масивне літеральне значення завжди має інше
|
|
12
|
+
* посилання, тож перевірка **завжди false**, незалежно від вмісту. Канон — `toEqual`
|
|
13
|
+
* (deep equality) для об'єктів і масивів (vitest-api-conventions.mdc, п.4).
|
|
14
|
+
* Знаходить лише `.toBe(` де аргумент — САМЕ літерал (нічого не приєднано після
|
|
15
|
+
* закриваючої дужки, окрім опційних пробілів і `)`) — `.toBe([...].join('\n'))`
|
|
16
|
+
* не матчиться: результат `.join()` — рядок-примітив, а не масив-посилання.
|
|
17
|
+
*/
|
|
18
|
+
const TO_BE_CALL_RE = /\.toBe\(/gu
|
|
19
|
+
/** Whitespace-символ (пробіл/таб/перенос) — для `skipWhitespace`, module scope (oxlint prefer-static-regex). */
|
|
20
|
+
const WS_RE = /\s/u
|
|
21
|
+
/** Рядкові/template-лапки, що відкривають літерал усередині `findMatchingBracketEnd`. */
|
|
22
|
+
const QUOTE_CHARS = new Set(['"', "'", '`'])
|
|
23
|
+
/** Мапа закриваючої дужки на парну дужку, яка її відкриває — для перевірки балансу стеку. */
|
|
24
|
+
const CLOSE_TO_OPEN = { '}': '{', ']': '[' }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Чи файл — JS-тест (`*.test.mjs` / `*.test.js`).
|
|
28
|
+
* @param {string} absPath абсолютний шлях
|
|
29
|
+
* @returns {boolean} `true` для `.test.{mjs,js}` файлів
|
|
30
|
+
*/
|
|
31
|
+
function isTestFile(absPath) {
|
|
32
|
+
const name = basename(absPath)
|
|
33
|
+
return name.endsWith('.test.mjs') || name.endsWith('.test.js')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Пропускає пробіли/переноси рядків, повертає індекс першого не-whitespace символу.
|
|
38
|
+
* @param {string} body вміст файлу
|
|
39
|
+
* @param {number} from стартовий індекс
|
|
40
|
+
* @returns {number} індекс першого значущого символу (може дорівнювати `body.length`)
|
|
41
|
+
*/
|
|
42
|
+
function skipWhitespace(body, from) {
|
|
43
|
+
let i = from
|
|
44
|
+
while (i < body.length && WS_RE.test(body[i])) i++
|
|
45
|
+
return i
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Просуває сканер на один крок усередині рядкового/template-літералу: обробляє
|
|
50
|
+
* екранування (`\x`) і повідомляє, чи саме на цьому символі рядок закрився.
|
|
51
|
+
* Виокремлено з `findMatchingBracketEnd`, аби тримати cognitive complexity
|
|
52
|
+
* головного циклу під порогом лінтера.
|
|
53
|
+
* @param {string} body вміст файлу
|
|
54
|
+
* @param {number} i індекс поточного символу (усередині рядка)
|
|
55
|
+
* @param {string} quote символ лапки, що відкриває поточний рядок
|
|
56
|
+
* @returns {{next: number, closed: boolean}} нова позиція сканера і прапорець закриття
|
|
57
|
+
*/
|
|
58
|
+
function stepInsideQuote(body, i, quote) {
|
|
59
|
+
const ch = body[i]
|
|
60
|
+
if (ch === '\\') return { next: i + 2, closed: false }
|
|
61
|
+
if (ch === quote) return { next: i + 1, closed: true }
|
|
62
|
+
return { next: i + 1, closed: false }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Знаходить індекс символу, що йде одразу за парною дужкою, яка закриває
|
|
67
|
+
* дужку на позиції `openIndex` (`{` чи `[`). Ігнорує дужки всередині
|
|
68
|
+
* рядкових/template-літералів, аби не збитись на `{ a: '}' }` тощо.
|
|
69
|
+
* @param {string} body вміст файлу
|
|
70
|
+
* @param {number} openIndex індекс відкриваючої дужки
|
|
71
|
+
* @returns {number|null} індекс одразу після закриваючої дужки, або `null` якщо не збалансовано
|
|
72
|
+
*/
|
|
73
|
+
function findMatchingBracketEnd(body, openIndex) {
|
|
74
|
+
const stack = [body[openIndex]]
|
|
75
|
+
let i = openIndex + 1
|
|
76
|
+
let quote = null
|
|
77
|
+
|
|
78
|
+
while (i < body.length) {
|
|
79
|
+
if (quote) {
|
|
80
|
+
const step = stepInsideQuote(body, i, quote)
|
|
81
|
+
if (step.closed) quote = null
|
|
82
|
+
i = step.next
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ch = body[i]
|
|
87
|
+
|
|
88
|
+
if (QUOTE_CHARS.has(ch)) {
|
|
89
|
+
quote = ch
|
|
90
|
+
i++
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (ch === '{' || ch === '[') {
|
|
95
|
+
stack.push(ch)
|
|
96
|
+
i++
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (ch in CLOSE_TO_OPEN) {
|
|
101
|
+
if (stack.at(-1) !== CLOSE_TO_OPEN[ch]) return null // незбалансовано — здаємось
|
|
102
|
+
stack.pop()
|
|
103
|
+
if (stack.length === 0) return i + 1
|
|
104
|
+
i++
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
i++
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Знаходить усі виклики `.toBe(` де перший аргумент — САМЕ об'єктний/масивний
|
|
116
|
+
* літерал (не результат ланцюжка викликів на ньому).
|
|
117
|
+
* @param {string} body вміст файлу
|
|
118
|
+
* @returns {Array<{line: number}>} знайдені порушення
|
|
119
|
+
*/
|
|
120
|
+
function findOffenders(body) {
|
|
121
|
+
const offenders = []
|
|
122
|
+
for (const m of body.matchAll(TO_BE_CALL_RE)) {
|
|
123
|
+
const argStart = skipWhitespace(body, m.index + m[0].length)
|
|
124
|
+
const ch = body[argStart]
|
|
125
|
+
if (ch !== '{' && ch !== '[') continue
|
|
126
|
+
|
|
127
|
+
const afterLiteral = findMatchingBracketEnd(body, argStart)
|
|
128
|
+
if (afterLiteral === null) continue
|
|
129
|
+
|
|
130
|
+
const afterWs = skipWhitespace(body, afterLiteral)
|
|
131
|
+
if (body[afterWs] !== ')') continue // приєднано щось після літерала (напр. .join(...)) — не порушення
|
|
132
|
+
|
|
133
|
+
const line = body.slice(0, m.index).split('\n').length
|
|
134
|
+
offenders.push({ line })
|
|
135
|
+
}
|
|
136
|
+
return offenders
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Detector: жоден `*.test.{mjs,js}` не викликає `expect(...).toBe(...)` з
|
|
141
|
+
* об'єктним/масивним літералом — `toBe` (Object.is) на новоствореному
|
|
142
|
+
* об'єкті/масиві завжди false; канон — `toEqual` (vitest-api-conventions.mdc, п.4).
|
|
143
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
144
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки з порушеннями.
|
|
145
|
+
*/
|
|
146
|
+
export async function lint(ctx) {
|
|
147
|
+
const reporter = createViolationReporter(ctx)
|
|
148
|
+
const { pass, fail } = reporter
|
|
149
|
+
|
|
150
|
+
const cwd = ctx.cwd
|
|
151
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
152
|
+
|
|
153
|
+
/** @type {string[]} */
|
|
154
|
+
const testFiles = []
|
|
155
|
+
await walkDir(
|
|
156
|
+
cwd,
|
|
157
|
+
absPath => {
|
|
158
|
+
if (isTestFile(absPath)) testFiles.push(absPath)
|
|
159
|
+
},
|
|
160
|
+
ignorePaths
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
/** @type {Array<{file: string, line: number}>} */
|
|
164
|
+
const offenders = []
|
|
165
|
+
for (const absPath of testFiles) {
|
|
166
|
+
const body = await readFile(absPath, 'utf8')
|
|
167
|
+
for (const o of findOffenders(body)) {
|
|
168
|
+
offenders.push({ file: relative(cwd, absPath), ...o })
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (offenders.length === 0) {
|
|
173
|
+
pass(`Жоден з ${testFiles.length} тестових файлів не викликає toBe(...) з об'єктним/масивним літералом (test.mdc)`)
|
|
174
|
+
return reporter.result()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const { file, line } of offenders) {
|
|
178
|
+
fail(
|
|
179
|
+
`${file}:${line}: expect(...).toBe(...) з об'єктним/масивним літералом завжди false ` +
|
|
180
|
+
`(Object.is на новому посиланні) — використовуй toEqual (test.mdc, vitest-api-conventions)`,
|
|
181
|
+
{ file }
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return reporter.result()
|
|
186
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
## Vitest API — обов'язкові конвенції (для генерації тестів LLM)
|
|
2
|
+
|
|
3
|
+
Цей розділ адресований генераторам тестів (LLM, local і cloud). Порушення будь-якого пункту — рівнозначне синтаксичній помилці: тест не запуститься або дасть хибно-позитивний результат.
|
|
4
|
+
|
|
5
|
+
Канон: тести виконуються через **vitest** під Bun-рушієм (`bun run --bun vitest run`, див. `test.package_json`), а не через нативний `bun test` — `import ... from 'bun:test'` заборонено (`no-bun-test-import`). API нижче — саме vitest, не bun:test і не jest.
|
|
6
|
+
|
|
7
|
+
### 1. Модульна система — лише ESM
|
|
8
|
+
|
|
9
|
+
`*.test.mjs` завжди ESM; `require()` у `.mjs` кидає `ERR_REQUIRE_ESM`.
|
|
10
|
+
|
|
11
|
+
✓ `import { foo } from '../foo.mjs'`
|
|
12
|
+
✗ `const { foo } = require('../foo.mjs')`
|
|
13
|
+
|
|
14
|
+
### 2. `vi.*` замість `jest.*`
|
|
15
|
+
|
|
16
|
+
Проект використовує Vitest, не Jest. Будь-який `jest.fn()`, `jest.mock()`, `jest.spyOn()` — помилка компіляції або runtime-помилка.
|
|
17
|
+
|
|
18
|
+
✓ `vi.fn()`, `vi.mock(...)`, `vi.spyOn(...)`
|
|
19
|
+
✗ `jest.fn()`, `jest.mock(...)`, `jest.spyOn(...)`
|
|
20
|
+
|
|
21
|
+
### 3. Кастинг mock-функцій — лише `vi.mocked()`
|
|
22
|
+
|
|
23
|
+
✓ `vi.mocked(fetch).mockResolvedValue(res)`
|
|
24
|
+
✗ `(fetch as vi.Mock).mockResolvedValue(res)` — TypeScript у `.mjs` не компілюється
|
|
25
|
+
|
|
26
|
+
### 4. `toBe` vs `toEqual`
|
|
27
|
+
|
|
28
|
+
`toBe` (Object.is) — для примітивів (`string`, `number`, `boolean`, `null`, `undefined`).
|
|
29
|
+
`toEqual` (deep equality) — для об'єктів і масивів.
|
|
30
|
+
|
|
31
|
+
`toBe({…})` для об'єктів **завжди false** (різні посилання) — використовуй `toEqual`.
|
|
32
|
+
|
|
33
|
+
✓ `expect(count).toBe(3)`
|
|
34
|
+
✓ `expect(result).toEqual({ ok: true })`
|
|
35
|
+
✗ `expect(result).toBe({ ok: true })`
|
|
36
|
+
|
|
37
|
+
**Перевірка** (`rules/test/vitest-api-conventions/main.mjs`): сканує `**/*.test.{mjs,js}`, шукає `expect(...).toBe(...)` з об'єктним/масивним літералом як першим аргументом — таке порівняння завжди `false` через reference equality, незалежно від вмісту.
|
|
38
|
+
|
|
39
|
+
### 5. Моки для Date / часу
|
|
40
|
+
|
|
41
|
+
`vi.spyOn(Date).mockReturnValue(…)` **не працює** з `new Date()` (TypeError: Date is not a constructor при `new`).
|
|
42
|
+
|
|
43
|
+
✓
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
vi.useFakeTimers()
|
|
47
|
+
vi.setSystemTime(new Date('2024-01-01'))
|
|
48
|
+
afterEach(() => vi.useRealTimers())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
✗ `vi.spyOn(global, 'Date').mockReturnValue(new Date('2024-01-01'))`
|
|
52
|
+
|
|
53
|
+
### 6. Моки для `process.env`
|
|
54
|
+
|
|
55
|
+
`process.env.X = '…'` забруднює глобальний стан; `vi.spyOn(process, 'env')` не дає ізоляції між тестами.
|
|
56
|
+
|
|
57
|
+
✓
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
vi.stubEnv('KEY', 'val')
|
|
61
|
+
afterEach(() => vi.unstubAllEnvs())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
✗ `process.env.KEY = 'val'` (без відновлення)
|
|
65
|
+
|
|
66
|
+
### 7. Модулі з top-level side-effect
|
|
67
|
+
|
|
68
|
+
Якщо source-модуль читає `process.env` або викликає функцію на рівні модуля — env/моки мають бути встановлені **до** `import`; сам import — dynamic.
|
|
69
|
+
|
|
70
|
+
✓
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
vi.stubEnv('KEY', 'val')
|
|
74
|
+
const { fn } = await import('../index.js')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
✗ статичний `import { fn } from '../index.js'` коли source викликає функцію на рівні модуля
|
|
78
|
+
|
|
79
|
+
### 8. Cleanup у `afterEach` — обов'язковий
|
|
80
|
+
|
|
81
|
+
Після кожного тест-файлу з mock-ами — відновлення стану:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
vi.restoreAllMocks()
|
|
86
|
+
vi.unstubAllEnvs()
|
|
87
|
+
vi.useRealTimers() // лише якщо vi.useFakeTimers() використовувався
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Пропуск `afterEach` — витік стану між тестами в одному файлі.
|
|
92
|
+
|
|
93
|
+
### 9. FormData у Node.js
|
|
94
|
+
|
|
95
|
+
`formData.get('field')` повертає `Blob | string | null`. Для file-полів — `toBeInstanceOf(Blob)`, НЕ `toBe('filename')`.
|
|
96
|
+
|
|
97
|
+
✓ `expect(formData.get('file')).toBeInstanceOf(Blob)`
|
|
98
|
+
✗ `expect(formData.get('file')).toBe('photo.jpg')`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### Канонічний шаблон тест-файлу
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
106
|
+
|
|
107
|
+
import { myFn } from '../my-module.mjs'
|
|
108
|
+
|
|
109
|
+
vi.mock('../dep.mjs', () => ({ dep: vi.fn() }))
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
vi.stubEnv('API_KEY', 'test-key')
|
|
113
|
+
vi.useFakeTimers()
|
|
114
|
+
vi.setSystemTime(new Date('2024-06-01'))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
vi.restoreAllMocks()
|
|
119
|
+
vi.unstubAllEnvs()
|
|
120
|
+
vi.useRealTimers()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe('myFn', () => {
|
|
124
|
+
it('повертає очікуване значення', async () => {
|
|
125
|
+
const result = await myFn()
|
|
126
|
+
expect(result).toEqual({ ok: true })
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
```
|
|
@@ -39,3 +39,21 @@ Rego-пакет: `text.cspell`
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
(відсутнє `language`, застарілий `version`, заборонений `@cspell/dict-`, не всі canonical `ignorePaths`)
|
|
42
|
+
|
|
43
|
+
## Локальні виключення та мови
|
|
44
|
+
|
|
45
|
+
Коли **cspell** підсвічує слово, спочатку **виправ текст**, а не розширюй словник:
|
|
46
|
+
|
|
47
|
+
- виправ **друкарські помилки** та неправильні форми;
|
|
48
|
+
- **перефразуй коректною українською** (або англійською, залежно від контексту файлу): заміни кальки й випадкові склади на звичні формулювання зі словників;
|
|
49
|
+
- заміни **жаргон**, якщо є природний еквівалент у тому ж стилі документації (наприклад, у коментарях пиши «функція зворотного виклику» замість розмовного запозичення з англійського `callback`).
|
|
50
|
+
|
|
51
|
+
У секцію `words` у `.cspell.json` додавай записи **лише якщо переписати коректно неможливо** або **недоречно**: власні назви, стабільні технічні терміни без усталеного перекладу в проєкті, ідентифікатори зовнішніх API тощо.
|
|
52
|
+
|
|
53
|
+
### Інші мови
|
|
54
|
+
|
|
55
|
+
**`@nitra/cspell-dict`** від `2.0.0` уже містить залежності на типові словники (`@cspell/dict-uk-ua`, `@cspell/dict-ru_ru` та інші) — якщо потрібна мова вже є серед них, додай лише код у поле `language` (наприклад `"en,uk,ru-ru,nitra"`), без окремих `@cspell/dict-*` у споживачі. Порядок у `import` може впливати на пріоритет словників — тримай корпоративний `@nitra/cspell-dict` першим, якщо додаєш інші розширення (рідко). Якщо мови немає в корпоративному пакеті — розширюй `@nitra/cspell-dict`, а не підключай `@cspell/dict-*` у корені репозиторію-споживача. Огляд upstream-словників: [streetsidesoftware/cspell-dicts](https://github.com/streetsidesoftware/cspell-dicts).
|
|
56
|
+
|
|
57
|
+
### Fix-режим: класифікація через omlx
|
|
58
|
+
|
|
59
|
+
cspell не має нативного `--fix`. Fix-режим **класифікує** знахідки через локальну LLM: detect → omlx-класифікація distinct-слів (bounded JSON-вихід) → валідні слова авто-дописуються у `.cspell.json#words` (sorted/dedup) → ймовірні одруки лишаються списком на ревʼю (не авто-виправляються). Максимум 80 distinct-слів за прогін.
|
|
@@ -38,3 +38,7 @@ Rego-пакет: `text.markdownlint`
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
|
+
|
|
42
|
+
## MD041 вимкнено навмисно
|
|
43
|
+
|
|
44
|
+
**MD041** (перший рядок файлу має бути top-level heading) вимкнено в каноні `.markdownlint-cli2.jsonc` навмисно: `.mdc`-файли починаються з YAML-frontmatter (`---`), не з `#`-заголовка, і MD041 хибно позначав би це порушенням. Деталі правил — [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## dotenv-linter: перевірка `.env`-файлів
|
|
2
|
+
|
|
3
|
+
**dotenv-linter** — швидкий лінтер для `.env`-файлів (перевіряє: LowercaseKey, DuplicatedKey, IncorrectDelimiter, UnorderedKey тощо). Інструмент має бути в **`PATH`** і **не** додається в `dependencies` / `devDependencies`.
|
|
4
|
+
|
|
5
|
+
Якщо `dotenv-linter` відсутній — встанови локально:
|
|
6
|
+
|
|
7
|
+
- **macOS:** `brew install dotenv-linter`
|
|
8
|
+
- **Linux:** `curl -sSfL https://git.io/JLbXn | sh -s -- -b /usr/local/bin`
|
|
9
|
+
- **cargo:** `cargo install dotenv-linter`
|
|
10
|
+
|
|
11
|
+
**Запуск:** авто-фікс — один прогін `dotenv-linter fix --no-backup --quiet` (інструмент сам застосовує всі виправлення, без потреби у diff/patch, на відміну від shellcheck), потім фінальний `dotenv-linter check --quiet`; будь-яке залишкове порушення — ненульовий код виходу.
|
|
12
|
+
|
|
13
|
+
Виключення (у full-режимі, рекурсивний `-r .`): `node_modules` і `.envrc` (direnv shell-синтаксис, не key=value формат). `.bak`-файли інструмент ігнорує самостійно. Якщо `.env*`-файлів немає, dotenv-linter повертає 0 («Nothing to check»).
|
|
14
|
+
|
|
15
|
+
**Застереження:** `.env`-файли зазвичай git-ignored, а delta-список — це змінені tracked-файли, тож ignored `.env` у задачний прогін не потрапляють; реальна перевірка `.env*` живе у full/CI прогонах. Tracked файли на кшталт `.env.example` delta таки ловить.
|
|
16
|
+
|
|
17
|
+
У CI — `--no-fix` (нуль мутацій): авто-фікс пропускається, лише `check`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## shellcheck: перевірка shell-скриптів
|
|
2
|
+
|
|
3
|
+
**shellcheck** — інструмент лише в **`PATH`**, **не** додавай до `dependencies` / `devDependencies`. VSCode-розширення — канон у `vscode_extensions` (`timonwong.shellcheck`).
|
|
4
|
+
|
|
5
|
+
Якщо `shellcheck` відсутній — встанови локально:
|
|
6
|
+
|
|
7
|
+
- **macOS:** `brew install shellcheck`
|
|
8
|
+
- **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`
|
|
9
|
+
- **Arch:** `sudo pacman -S shellcheck`
|
|
10
|
+
|
|
11
|
+
Потрібен також **`patch`** у `PATH` для авто-фіксу (на macOS зазвичай уже є; на Linux: `sudo apt-get install -y patch`). У read-only режимі (детект, CI) відсутність `patch` не блокує перевірку — лише авто-фікс.
|
|
12
|
+
|
|
13
|
+
ShellCheck не має прапорця `--fix`. Авто-виправлення — цикл `shellcheck -f diff` + `patch -p1` (до 32 ітерацій на файл, захист від зациклення), потім звичайний повний прогін `shellcheck` по всіх зібраних файлах для фінальної перевірки.
|
|
14
|
+
|
|
15
|
+
Список файлів: у git-робочому дереві — `git ls-files` з pathspec `:(glob)**/*.sh`; інакше — `globSync` з виключенням `node_modules`. Якщо скриптів не знайдено — вихід 0.
|
|
16
|
+
|
|
17
|
+
Delta-лінт (`npx @7n/rules lint`) перевіряє лише змінені `*.sh`-файли; повний прогін (`--full`) — усі tracked `*.sh` у репо.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## v8r: валідація JSON/YAML/TOML за Schema Store
|
|
2
|
+
|
|
3
|
+
**`v8r`** — інструмент валідації файлів за схемами з [Schema Store](https://www.schemastore.org/). Використовуй лише через **`bunx v8r`**, не в `devDependencies`.
|
|
4
|
+
|
|
5
|
+
Canonical запуск — через lint-адаптер `n-rules lint text`: під капотом окремий каталог схем `npm/schemas/v8r-catalog.json` пакета `@7n/rules` (локальні схеми у `npm/schemas/` і `npm/schemas/vendor/`, self-contained, без мережевих `$ref`) підставляється в v8r як `customCatalog`, щоб перевірка лишалась офлайн. Delta-режим валідує один виклик на конкретний список змінених `.json`/`.json5`/`.yml`/`.yaml`/`.toml`-файлів; full-режим — по одному виклику `bunx v8r <glob>` на кожен тип розширення (v8r падає з кодом **98**, якщо переданий glob порожній, — саме тому не можна об'єднати всі розширення в один виклик).
|
|
6
|
+
|
|
7
|
+
Якщо для файлу немає запису в локальному каталозі, v8r іде мережевим fallback на `schemastore.org` — це не блокує прогін, але свідчить, що каталог варто розширити.
|
|
8
|
+
|
|
9
|
+
### `.v8rignore`
|
|
10
|
+
|
|
11
|
+
У корені проєкту має бути **`.v8rignore`**: для JSON без стабільної схеми в Schema Store перевірка інакше завершується помилкою. Мінімум виключи **`.vscode/extensions.json`** і **`.vscode/settings.json`** (немає стабільної схеми в каталозі) — це перевіряється окремо, FS-existence-концерном `text/formatting`.
|
|
12
|
+
|
|
13
|
+
```text title=".v8rignore"
|
|
14
|
+
.vscode/extensions.json
|
|
15
|
+
.vscode/settings.json
|
|
16
|
+
.git/**
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Не розширюй ignore, щоб приховати проблеми там, де схема є. За потреби додай **`.git/**`** (службові JSON під `.git`) та інші проєктні JSON без стабільної схеми в ланцюжку v8r (наприклад `.zed/settings.json`).
|
|
20
|
+
|
|
21
|
+
### Запуск без обгортки
|
|
22
|
+
|
|
23
|
+
Чотири виклики `(bunx v8r "<glob>" || [ $? -eq 98 ])` для **`**/*.json`**, **`**/*.yml`**, **`**/*.yaml`**, **`**/*.toml`** (за потреби окремо **json5**). Враховує `.gitignore`. Для власного каталогу схем — прапорець [`--catalogs`](https://chris48s.github.io/v8r/usage-examples/#using-a-custom-catlog).
|
|
@@ -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`).
|