@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,88 @@
|
|
|
1
|
+
## Адмінські таблиці — n-admin-table
|
|
2
|
+
|
|
3
|
+
Для таблиць, що мають займати всю висоту сторінки (або блоку), додавай клас `n-admin-table` і атрибути `hide-no-data`, `dense`, `flat`. Таблиця автоматично розтягнеться на весь доступний простір. У блоках меншої висоти — задай їм явний `height`.
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<!-- ТАБЛИЦЯ -->
|
|
7
|
+
<q-table class="n-admin-table" hide-no-data flat dense />
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Якщо клас `.n-admin-table` не визначено в `app.scss` — додай:
|
|
11
|
+
|
|
12
|
+
```scss
|
|
13
|
+
// ADMIN TABLE
|
|
14
|
+
.n-admin-table {
|
|
15
|
+
height: calc(100vh - 106px);
|
|
16
|
+
|
|
17
|
+
thead {
|
|
18
|
+
position: sticky;
|
|
19
|
+
z-index: 3;
|
|
20
|
+
top: 0;
|
|
21
|
+
|
|
22
|
+
tr th {
|
|
23
|
+
background-color: $grey-3;
|
|
24
|
+
height: 36px !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// tbody td {
|
|
29
|
+
// font-size: 14px;
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
tbody tr:last-child td {
|
|
33
|
+
border-bottom: 1px solid rgb(0 0 0 / 12%) !important;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
th:first-child,
|
|
37
|
+
td:first-child,
|
|
38
|
+
th:last-child,
|
|
39
|
+
td:last-child {
|
|
40
|
+
width: 1px;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.q-table__bottom {
|
|
45
|
+
background-color: $grey-3;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.q-table__progress th {
|
|
49
|
+
height: 1px !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.text-wrap {
|
|
53
|
+
max-width: 250px;
|
|
54
|
+
white-space: normal;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// sticky columns
|
|
58
|
+
td.sticky-left {
|
|
59
|
+
position: sticky;
|
|
60
|
+
left: 0;
|
|
61
|
+
z-index: 2;
|
|
62
|
+
background-color: white;
|
|
63
|
+
box-shadow: 2px 0 5px #0002;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
td.sticky-right {
|
|
67
|
+
position: sticky;
|
|
68
|
+
right: 0;
|
|
69
|
+
z-index: 2;
|
|
70
|
+
background-color: white;
|
|
71
|
+
box-shadow: -2px 0 5px #0002;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
th.sticky-left {
|
|
75
|
+
position: sticky;
|
|
76
|
+
left: 0;
|
|
77
|
+
z-index: 3;
|
|
78
|
+
box-shadow: 2px 0 5px #0002;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
th.sticky-right {
|
|
82
|
+
position: sticky;
|
|
83
|
+
right: 0;
|
|
84
|
+
z-index: 3;
|
|
85
|
+
box-shadow: -2px 0 5px #0002;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/style/admin_table/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 9e484318
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Детектор concern-а `admin_table` (read-only, whole-repo): якщо десь у `.vue`-файлі
|
|
12
|
+
використано клас `n-admin-table`, він має бути визначений хоч в одному `.scss`/`.css`/`.vue`
|
|
13
|
+
файлі проєкту, інакше — порушення `missing-admin-table-style`. Потребує whole-repo
|
|
14
|
+
сканування (usage і definition можуть бути в різних файлах), тому не залежить від `ctx.files`.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
5
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
6
|
+
|
|
7
|
+
const RELEVANT_RE = /\.(?:vue|scss|css)$/u
|
|
8
|
+
const USAGE_RE = /\bn-admin-table\b/u
|
|
9
|
+
const DEFINITION_RE = /\.n-admin-table\b/u
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detector style/admin_table (read-only, whole-repo): якщо десь у `.vue` використано
|
|
13
|
+
* клас `n-admin-table`, він має бути визначений хоч в одному `.scss`/`.css`/`.vue`
|
|
14
|
+
* (guide: `admin_table.mdc`). Для усунення false positives — крос-файлова перевірка,
|
|
15
|
+
* тож потребує whole-repo сканування незалежно від `ctx.files`.
|
|
16
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
|
|
17
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
18
|
+
*/
|
|
19
|
+
export async function lint(ctx) {
|
|
20
|
+
const reporter = createViolationReporter(ctx)
|
|
21
|
+
const { fail } = reporter
|
|
22
|
+
const cwd = ctx.cwd
|
|
23
|
+
|
|
24
|
+
/** @type {string[]} */
|
|
25
|
+
const files = []
|
|
26
|
+
await walkDir(cwd, f => {
|
|
27
|
+
if (RELEVANT_RE.test(f)) files.push(f)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
let used = false
|
|
31
|
+
let defined = false
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
const content = await readFile(file, 'utf8')
|
|
34
|
+
if (!used && file.endsWith('.vue') && USAGE_RE.test(content)) used = true
|
|
35
|
+
if (!defined && DEFINITION_RE.test(content)) defined = true
|
|
36
|
+
if (used && defined) break
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (used && !defined) {
|
|
40
|
+
fail(
|
|
41
|
+
'Клас `.n-admin-table` використовується у `.vue`, але не визначений у жодному `.scss`/`.css` (guide: style/admin_table.mdc) — додай фікс до app.scss',
|
|
42
|
+
'missing-admin-table-style'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return reporter.result()
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Кольори: quasar-variables.scss і app.scss
|
|
2
|
+
|
|
3
|
+
Основні кольори визначай виключно через стандартні Quasar-змінні: `$primary`, `$secondary`, `$accent`, `$positive`, `$negative`, `$warning`.
|
|
4
|
+
|
|
5
|
+
Якщо потрібен додатковий колір — визнач його в `quasar-variables.scss`, і **обов'язково** додай до `app.scss` відповідні `.text-*` / `.bg-*` класи:
|
|
6
|
+
|
|
7
|
+
```scss
|
|
8
|
+
/* quasar-variables.scss */
|
|
9
|
+
$white-a1: color.adjust(white, $alpha: -0.85);
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```scss
|
|
13
|
+
/* app.scss */
|
|
14
|
+
.text-white-a1 {
|
|
15
|
+
color: $white-a1 !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.bg-white-a1 {
|
|
19
|
+
background-color: $white-a1 !important;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/style/gap/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: dcba23e0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Детектор concern-а `gap` (read-only, whole-repo): кожен суфікс утиліти
|
|
12
|
+
`.n-gap-{xs,sm,md,lg}`, використаний у `.vue`-файлі, має бути визначений хоч в одному
|
|
13
|
+
`.scss`/`.css`/`.vue` файлі проєкту — інакше порушення `missing-gap-style` для цього
|
|
14
|
+
суфіксу. Суфікси відстежуються незалежно один від одного, тож часткове покриття все одно
|
|
15
|
+
виявляє відсутній. Whole-repo сканування, не залежить від `ctx.files`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Gap між flex/grid-елементами — .n-gap-*
|
|
2
|
+
|
|
3
|
+
Для відступів між плаваючими елементами (flex, grid) використовуй класи `.n-gap-*` замість `q-gutter-*`. Якщо класів немає в `app.scss` — додай:
|
|
4
|
+
|
|
5
|
+
```scss
|
|
6
|
+
// GAP
|
|
7
|
+
.n-gap-xs {
|
|
8
|
+
gap: 4px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.n-gap-sm {
|
|
12
|
+
gap: 8px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.n-gap-md {
|
|
16
|
+
gap: 16px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.n-gap-lg {
|
|
20
|
+
gap: 24px;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
5
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
6
|
+
|
|
7
|
+
const RELEVANT_RE = /\.(?:vue|scss|css)$/u
|
|
8
|
+
const USAGE_RE = /\bn-gap-(xs|sm|md|lg)\b/gu
|
|
9
|
+
const DEFINITION_RE = /\.n-gap-(xs|sm|md|lg)\b/gu
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detector style/gap (read-only, whole-repo): кожен суфікс `.n-gap-{xs,sm,md,lg}`,
|
|
13
|
+
* використаний у `.vue`, має бути визначений хоч в одному `.scss`/`.css`/`.vue`
|
|
14
|
+
* (guide: `gap.mdc`). Крос-файлова перевірка usage↔definition — потребує whole-repo
|
|
15
|
+
* сканування незалежно від `ctx.files`.
|
|
16
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
|
|
17
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
18
|
+
*/
|
|
19
|
+
export async function lint(ctx) {
|
|
20
|
+
const reporter = createViolationReporter(ctx)
|
|
21
|
+
const { fail } = reporter
|
|
22
|
+
const cwd = ctx.cwd
|
|
23
|
+
|
|
24
|
+
/** @type {string[]} */
|
|
25
|
+
const files = []
|
|
26
|
+
await walkDir(cwd, f => {
|
|
27
|
+
if (RELEVANT_RE.test(f)) files.push(f)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/** @type {Set<string>} */
|
|
31
|
+
const used = new Set()
|
|
32
|
+
/** @type {Set<string>} */
|
|
33
|
+
const defined = new Set()
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const content = await readFile(file, 'utf8')
|
|
36
|
+
if (file.endsWith('.vue')) {
|
|
37
|
+
for (const m of content.matchAll(USAGE_RE)) used.add(m[1])
|
|
38
|
+
}
|
|
39
|
+
for (const m of content.matchAll(DEFINITION_RE)) defined.add(m[1])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const suffix of used) {
|
|
43
|
+
if (!defined.has(suffix)) {
|
|
44
|
+
fail(
|
|
45
|
+
`Клас \`.n-gap-${suffix}\` використовується у \`.vue\`, але не визначений у жодному \`.scss\`/\`.css\` (guide: style/gap.mdc) — додай клас до app.scss`,
|
|
46
|
+
'missing-gap-style'
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return reporter.result()
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Quasar як стильова основа
|
|
2
|
+
|
|
3
|
+
У Vue-проектах використовуй **Quasar** як базову стильову систему:
|
|
4
|
+
|
|
5
|
+
- **Класи компонентів:** `q-pa-*`, `q-ma-*`, `q-mt-*`, `q-mb-*`, `row`, `col`, `col-*`, `items-center`, `justify-between`, `text-*`, `bg-*`, `shadow-*` тощо — стандартні утиліти Quasar.
|
|
6
|
+
- **Кольори:** використовуй Quasar CSS-змінні через класи (`text-primary`, `bg-accent`, `text-negative` тощо) або через SCSS-змінні (`$primary`, `$accent`) у `.scss`-файлах.
|
|
7
|
+
- **Не пиши власні утиліти**, якщо є відповідний Quasar-клас.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/style/quasar_fixes/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 3eecca4c
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Детектор concern-а `quasar_fixes` (read-only, whole-repo): якщо `.vue`-файл використовує
|
|
12
|
+
`<q-scroll-area>` або `<q-tooltip>`, десь у `.scss`/`.css`/`.vue` проєкту має бути
|
|
13
|
+
відповідний CSS-фікс (`.q-scrollarea` / `.q-tooltip`) — інакше порушення
|
|
14
|
+
`missing-quasar-fix`. iOS-zoom-фікс (тригер на `input`/`textarea`/`select`) навмисно не
|
|
15
|
+
перевіряється — надто загальний тригер, false-positive на майже будь-якій формі. Whole-repo
|
|
16
|
+
сканування, не залежить від `ctx.files`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
5
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
6
|
+
|
|
7
|
+
const RELEVANT_RE = /\.(?:vue|scss|css)$/u
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Пари (usage у `.vue` → очікуваний CSS-фікс) з guide `quasar_fixes.mdc`. Навмисно НЕ
|
|
11
|
+
* включено iOS-zoom-фікс (тригер `input`/`textarea`/`select` — занадто загальний,
|
|
12
|
+
* false-positive на майже будь-якій формі; консервативно залишено лише narrative).
|
|
13
|
+
*/
|
|
14
|
+
const FIXES = [
|
|
15
|
+
{ usage: /<q-scroll-area\b/u, definition: /\.q-scrollarea\b/u, name: 'q-scroll-area', selector: '.q-scrollarea' },
|
|
16
|
+
{ usage: /<q-tooltip\b/u, definition: /\.q-tooltip\b/u, name: 'q-tooltip', selector: '.q-tooltip' }
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detector style/quasar_fixes (read-only, whole-repo): якщо `.vue` використовує
|
|
21
|
+
* `<q-scroll-area>` або `<q-tooltip>`, у `.scss`/`.css`/`.vue` має бути відповідний
|
|
22
|
+
* CSS-фікс (guide: `quasar_fixes.mdc`). Крос-файлова перевірка usage↔definition —
|
|
23
|
+
* потребує whole-repo сканування незалежно від `ctx.files`.
|
|
24
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
|
|
25
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
26
|
+
*/
|
|
27
|
+
export async function lint(ctx) {
|
|
28
|
+
const reporter = createViolationReporter(ctx)
|
|
29
|
+
const { fail } = reporter
|
|
30
|
+
const cwd = ctx.cwd
|
|
31
|
+
|
|
32
|
+
/** @type {string[]} */
|
|
33
|
+
const files = []
|
|
34
|
+
await walkDir(cwd, f => {
|
|
35
|
+
if (RELEVANT_RE.test(f)) files.push(f)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const used = new Set()
|
|
39
|
+
const defined = new Set()
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const content = await readFile(file, 'utf8')
|
|
42
|
+
for (const fix of FIXES) {
|
|
43
|
+
if (file.endsWith('.vue') && !used.has(fix.name) && fix.usage.test(content)) used.add(fix.name)
|
|
44
|
+
if (!defined.has(fix.name) && fix.definition.test(content)) defined.add(fix.name)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const fix of FIXES) {
|
|
49
|
+
if (used.has(fix.name) && !defined.has(fix.name)) {
|
|
50
|
+
fail(
|
|
51
|
+
`Компонент \`${fix.name}\` використовується у \`.vue\`, але фікс \`${fix.selector}\` відсутній у \`.scss\`/\`.css\` (guide: style/quasar_fixes.mdc) — додай фікс до app.scss`,
|
|
52
|
+
'missing-quasar-fix'
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return reporter.result()
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Фікси компонентів Quasar
|
|
2
|
+
|
|
3
|
+
При використанні компонента `q-scroll-area` або `q-tooltip` додай до `app.scss` відповідні фікси. Аналогічно — фікс масштабування на iOS.
|
|
4
|
+
|
|
5
|
+
```scss
|
|
6
|
+
// FIX стилі для прокручуваної області по висоті екрана
|
|
7
|
+
.q-scrollarea {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
height: auto;
|
|
11
|
+
min-height: 0;
|
|
12
|
+
max-height: 100%;
|
|
13
|
+
|
|
14
|
+
.scroll {
|
|
15
|
+
flex: 10000 1 0%;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// FIX tooltip
|
|
20
|
+
.q-tooltip {
|
|
21
|
+
font-size: 1em;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// FIX не зумувати на iOS
|
|
25
|
+
@media (width <= 600px) {
|
|
26
|
+
input,
|
|
27
|
+
textarea,
|
|
28
|
+
select {
|
|
29
|
+
font-size: 16px;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
+
"lint": {
|
|
4
|
+
"scope": "full",
|
|
5
|
+
"glob": [
|
|
6
|
+
"**/src/**/*.js",
|
|
7
|
+
"**/src/**/*.ts",
|
|
8
|
+
"**/src/**/*.jsx",
|
|
9
|
+
"**/src/**/*.tsx",
|
|
10
|
+
"**/src/**/*.vue",
|
|
11
|
+
"**/src-tauri/Cargo.toml",
|
|
12
|
+
"**/src-tauri/src/lib.rs",
|
|
13
|
+
"**/src-tauri/capabilities/*.json"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: npm/rules/tauri/tool_surface/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 87b28b8d
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Детектор concern-а `tool_surface`: для кожного Tauri-workspace знаходить Tauri-плагіни,
|
|
12
|
+
яких торкається JS/TS/Vue-код (через `@tauri-apps/plugin-*` import або прямий
|
|
13
|
+
`invoke('plugin:<slug>|...')`), і для кожного такого плагіна перевіряє три незалежні умови —
|
|
14
|
+
(1) crate-залежність присутня в `src-tauri/Cargo.toml`, (2) rust-ідентифікатор
|
|
15
|
+
(`tauri_plugin_<slug>`) згаданий у `src-tauri/src/lib.rs` (зареєстрований у builder-і),
|
|
16
|
+
(3) permission для нього є в `src-tauri/capabilities/*.json`. Відсутність будь-якої з трьох —
|
|
17
|
+
тихий рантайм-фейл без помилки компіляції, тому кожна дає окрему violation
|
|
18
|
+
(`tool-surface-plugin-dep-missing` / `-not-registered` / `-capability-missing`).
|
|
19
|
+
|
|
20
|
+
## Публічний API
|
|
21
|
+
|
|
22
|
+
`PLUGIN_DEP_MISSING`, `PLUGIN_NOT_REGISTERED`, `PLUGIN_CAPABILITY_MISSING` — константи
|
|
23
|
+
причин violation. `lint(ctx)` — точка входу concern-у: обходить усі Tauri app workspaces
|
|
24
|
+
(`findTauriAppWorkspaces`) і перевіряє кожен.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/** @see ./docs/main.md */
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { globby } from 'globby'
|
|
7
|
+
|
|
8
|
+
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
9
|
+
import { findTauriAppWorkspaces, groupCargoDepsBySection } from '../updater/main.mjs'
|
|
10
|
+
|
|
11
|
+
/** JS торкається плагіна, але crate відсутній у `src-tauri/Cargo.toml`. */
|
|
12
|
+
export const PLUGIN_DEP_MISSING = 'tool-surface-plugin-dep-missing'
|
|
13
|
+
/** JS торкається плагіна, але його rust-ідентифікатор ніде не згадується в `lib.rs`. */
|
|
14
|
+
export const PLUGIN_NOT_REGISTERED = 'tool-surface-plugin-not-registered'
|
|
15
|
+
/** JS торкається плагіна, але жодна `capabilities/*.json` не дає йому permission. */
|
|
16
|
+
export const PLUGIN_CAPABILITY_MISSING = 'tool-surface-plugin-capability-missing'
|
|
17
|
+
|
|
18
|
+
const JS_PLUGIN_IMPORT_RE = /@tauri-apps\/plugin-([a-z0-9-]+)/gu
|
|
19
|
+
const INVOKE_PLUGIN_RE = /invoke\(\s*['"]plugin:([a-z0-9-]+)\|/gu
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Збирає slugs Tauri-плагінів, яких торкається JS/TS/Vue-код через wrapper-пакет
|
|
23
|
+
* `@tauri-apps/plugin-*` (`import ... from '@tauri-apps/plugin-dialog'`) або прямий
|
|
24
|
+
* `invoke('plugin:<slug>|<command>')`. Це "тули" в термінах n-tool-surface — call
|
|
25
|
+
* surface, яким користується UI/оркестратор/LLM.
|
|
26
|
+
* @param {string} srcDir абсолютний шлях до `<ws>/src`
|
|
27
|
+
* @returns {Promise<Set<string>>} множина slugs (`dialog`, `http`, …)
|
|
28
|
+
*/
|
|
29
|
+
async function collectPluginUsages(srcDir) {
|
|
30
|
+
const slugs = new Set()
|
|
31
|
+
if (!existsSync(srcDir)) return slugs
|
|
32
|
+
|
|
33
|
+
const files = await globby(['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.vue'], {
|
|
34
|
+
cwd: srcDir,
|
|
35
|
+
onlyFiles: true,
|
|
36
|
+
gitignore: false
|
|
37
|
+
})
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const content = await readFile(join(srcDir, file), 'utf8')
|
|
40
|
+
for (const m of content.matchAll(JS_PLUGIN_IMPORT_RE)) slugs.add(m[1])
|
|
41
|
+
for (const m of content.matchAll(INVOKE_PLUGIN_RE)) slugs.add(m[1])
|
|
42
|
+
}
|
|
43
|
+
return slugs
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Збирає всі permission-ідентифікатори з `capabilities/*.json` workspace-каталогу.
|
|
48
|
+
* @param {string} capDir абсолютний шлях до каталогу capabilities
|
|
49
|
+
* @returns {Promise<Set<string>>} множина permission-ідентифікаторів
|
|
50
|
+
*/
|
|
51
|
+
async function collectCapabilityPermissionIds(capDir) {
|
|
52
|
+
const ids = new Set()
|
|
53
|
+
if (!existsSync(capDir)) return ids
|
|
54
|
+
|
|
55
|
+
const files = await globby('*.json', { cwd: capDir, onlyFiles: true, gitignore: false })
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
let cap
|
|
58
|
+
try {
|
|
59
|
+
cap = JSON.parse(await readFile(join(capDir, file), 'utf8'))
|
|
60
|
+
} catch {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
const perms = Array.isArray(cap.permissions) ? cap.permissions : []
|
|
64
|
+
for (const p of perms) {
|
|
65
|
+
const id = typeof p === 'string' ? p : p?.identifier
|
|
66
|
+
if (id) ids.add(id)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return ids
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Перевіряє один workspace: кожен Tauri-плагін, якого JS торкається через
|
|
74
|
+
* wrapper-пакет `@tauri-apps/plugin-*` чи прямий `invoke('plugin:...')`, має бути
|
|
75
|
+
* (1) залежністю в Cargo.toml, (2) згаданий у lib.rs (реєстрація builder-а), (3) мати
|
|
76
|
+
* permission у capabilities/*.json — інакше виклик тихо падає в рантаймі, без
|
|
77
|
+
* компіляційної помилки (tauri.mdc tool_surface).
|
|
78
|
+
* @param {string} ws відносний шлях workspace
|
|
79
|
+
* @param {string} cwd корінь репо
|
|
80
|
+
* @param {ReturnType<typeof createViolationReporter>} reporter репортер концерну
|
|
81
|
+
* @returns {Promise<void>} завершується після перевірки
|
|
82
|
+
*/
|
|
83
|
+
async function checkWorkspace(ws, cwd, reporter) {
|
|
84
|
+
const base = ws === '.' ? cwd : join(cwd, ws)
|
|
85
|
+
const srcDir = join(base, 'src')
|
|
86
|
+
const usedSlugs = await collectPluginUsages(srcDir)
|
|
87
|
+
if (usedSlugs.size === 0) return
|
|
88
|
+
|
|
89
|
+
const srcTauriDir = join(base, 'src-tauri')
|
|
90
|
+
const cargoPath = join(srcTauriDir, 'Cargo.toml')
|
|
91
|
+
const libRsPath = join(srcTauriDir, 'src', 'lib.rs')
|
|
92
|
+
const capDir = join(srcTauriDir, 'capabilities')
|
|
93
|
+
|
|
94
|
+
const bySection = existsSync(cargoPath) ? groupCargoDepsBySection(await readFile(cargoPath, 'utf8')) : new Map()
|
|
95
|
+
const libRsContent = existsSync(libRsPath) ? await readFile(libRsPath, 'utf8') : ''
|
|
96
|
+
const capIds = await collectCapabilityPermissionIds(capDir)
|
|
97
|
+
|
|
98
|
+
const cargoRel = cargoPath.slice(cwd.length + 1)
|
|
99
|
+
const libRsRel = libRsPath.slice(cwd.length + 1)
|
|
100
|
+
const capRel = capDir.slice(cwd.length + 1)
|
|
101
|
+
|
|
102
|
+
const depKeysList = bySection.values().toArray()
|
|
103
|
+
|
|
104
|
+
for (const slug of [...usedSlugs].toSorted()) {
|
|
105
|
+
const cargoDep = `tauri-plugin-${slug}`
|
|
106
|
+
const rustIdent = `tauri_plugin_${slug.replaceAll('-', '_')}`
|
|
107
|
+
|
|
108
|
+
if (depKeysList.every(keys => !keys.includes(cargoDep))) {
|
|
109
|
+
reporter.fail(
|
|
110
|
+
`${cargoRel}: JS звертається до плагіна "${slug}" (@tauri-apps/plugin-${slug} чи invoke('plugin:${slug}|...')), але "${cargoDep}" відсутній у Cargo.toml (tauri.mdc tool_surface)`,
|
|
111
|
+
{ reason: PLUGIN_DEP_MISSING, file: cargoRel, data: { slug } }
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!libRsContent.includes(rustIdent)) {
|
|
116
|
+
reporter.fail(
|
|
117
|
+
`${libRsRel}: плагін "${slug}" ніде не згадується — invoke('plugin:${slug}|...') з UI тихо впаде, бо builder.plugin(${rustIdent}::…) не зареєстрований (tauri.mdc tool_surface)`,
|
|
118
|
+
{ reason: PLUGIN_NOT_REGISTERED, file: libRsRel, data: { slug } }
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (capIds.values().every(id => id !== slug && !id.startsWith(`${slug}:`))) {
|
|
123
|
+
reporter.fail(
|
|
124
|
+
`${capRel}/*.json: бракує permission "${slug}:*" для плагіна, якого торкається JS — invoke тихо падає permission-denied, видно лише в console.error (tauri.mdc tool_surface)`,
|
|
125
|
+
{ reason: PLUGIN_CAPABILITY_MISSING, file: capRel, data: { slug } }
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
|
|
133
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки
|
|
134
|
+
*/
|
|
135
|
+
export async function lint(ctx) {
|
|
136
|
+
const cwd = ctx.cwd
|
|
137
|
+
const reporter = createViolationReporter(ctx)
|
|
138
|
+
|
|
139
|
+
const apps = await findTauriAppWorkspaces(cwd)
|
|
140
|
+
for (const ws of apps) {
|
|
141
|
+
await checkWorkspace(ws, cwd, reporter)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return reporter.result()
|
|
145
|
+
}
|