@7n/rules-lang-js 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/rules/style/admin_table/admin_table.mdc +88 -0
  4. package/rules/style/admin_table/concern.json +7 -0
  5. package/rules/style/admin_table/docs/index.md +9 -0
  6. package/rules/style/admin_table/docs/main.md +14 -0
  7. package/rules/style/admin_table/main.mjs +46 -0
  8. package/rules/style/colors/colors.mdc +21 -0
  9. package/rules/style/colors/concern.json +3 -0
  10. package/rules/style/docs/index.md +11 -0
  11. package/rules/style/gap/concern.json +7 -0
  12. package/rules/style/gap/docs/index.md +9 -0
  13. package/rules/style/gap/docs/main.md +15 -0
  14. package/rules/style/gap/gap.mdc +22 -0
  15. package/rules/style/gap/main.mjs +51 -0
  16. package/rules/style/lint/concern.json +7 -0
  17. package/rules/style/lint/docs/fix-lint.md +29 -0
  18. package/rules/style/lint/docs/index.md +11 -0
  19. package/rules/style/lint/docs/main.md +29 -0
  20. package/rules/style/lint/fix-lint.mjs +66 -0
  21. package/rules/style/lint/main.mjs +68 -0
  22. package/rules/style/main.json +1 -0
  23. package/rules/style/main.mdc +12 -0
  24. package/rules/style/package_json/concern.json +9 -0
  25. package/rules/style/package_json/docs/fix-package_json.md +26 -0
  26. package/rules/style/package_json/docs/index.md +9 -0
  27. package/rules/style/package_json/fix-package_json.mjs +3 -0
  28. package/rules/style/package_json/package_json.mdc +18 -0
  29. package/rules/style/package_json/package_json.rego +31 -0
  30. package/rules/style/package_json/template/package.json.snippet.json +5 -0
  31. package/rules/style/quasar/concern.json +3 -0
  32. package/rules/style/quasar/quasar.mdc +7 -0
  33. package/rules/style/quasar_fixes/concern.json +7 -0
  34. package/rules/style/quasar_fixes/docs/index.md +9 -0
  35. package/rules/style/quasar_fixes/docs/main.md +16 -0
  36. package/rules/style/quasar_fixes/main.mjs +57 -0
  37. package/rules/style/quasar_fixes/quasar_fixes.mdc +32 -0
  38. package/rules/style/tooling/concern.json +14 -0
  39. package/rules/style/tooling/docs/fix-tooling.md +29 -0
  40. package/rules/style/tooling/docs/index.md +12 -0
  41. package/rules/style/tooling/docs/main.md +34 -0
  42. package/rules/style/tooling/fix-tooling.mjs +58 -0
  43. package/rules/style/tooling/main.mjs +73 -0
  44. package/rules/style/tooling/tooling.mdc +85 -0
  45. package/rules/style/vscode_extensions/concern.json +9 -0
  46. package/rules/style/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  47. package/rules/style/vscode_extensions/docs/index.md +11 -0
  48. package/rules/style/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  49. package/rules/style/vscode_extensions/template/extensions.json.snippet.json +1 -0
  50. package/rules/style/vscode_extensions/vscode_extensions.mdc +13 -0
  51. package/rules/style/vscode_extensions/vscode_extensions.rego +13 -0
  52. package/rules/style/vscode_settings/concern.json +9 -0
  53. package/rules/style/vscode_settings/docs/fix-vscode_settings.md +26 -0
  54. package/rules/style/vscode_settings/docs/index.md +9 -0
  55. package/rules/style/vscode_settings/fix-vscode_settings.mjs +5 -0
  56. package/rules/style/vscode_settings/template/settings.json.snippet.json +5 -0
  57. package/rules/style/vscode_settings/vscode_settings.mdc +19 -0
  58. package/rules/style/vscode_settings/vscode_settings.rego +15 -0
  59. package/rules/test/docs/index.md +11 -0
  60. package/rules/test/lib/collect-test-file-offenders.mjs +49 -0
  61. package/rules/test/lib/docs/collect-test-file-offenders.md +29 -0
  62. package/rules/test/lib/docs/index.md +9 -0
  63. package/rules/test/location/concern.json +8 -0
  64. package/rules/test/location/docs/index.md +11 -0
  65. package/rules/test/location/docs/main.md +36 -0
  66. package/rules/test/location/location.mdc +52 -0
  67. package/rules/test/location/main.mjs +70 -0
  68. package/rules/test/main.json +1 -0
  69. package/rules/test/main.mdc +26 -0
  70. package/rules/test/no-bun-test-import/concern.json +7 -0
  71. package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +30 -0
  72. package/rules/test/no-bun-test-import/docs/index.md +10 -0
  73. package/rules/test/no-bun-test-import/docs/main.md +35 -0
  74. package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
  75. package/rules/test/no-bun-test-import/main.mjs +105 -0
  76. package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
  77. package/rules/test/no-console-store-restore/concern.json +7 -0
  78. package/rules/test/no-console-store-restore/docs/index.md +11 -0
  79. package/rules/test/no-console-store-restore/docs/main.md +44 -0
  80. package/rules/test/no-console-store-restore/main.mjs +55 -0
  81. package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
  82. package/rules/test/no-process-chdir/concern.json +7 -0
  83. package/rules/test/no-process-chdir/docs/index.md +11 -0
  84. package/rules/test/no-process-chdir/docs/main.md +32 -0
  85. package/rules/test/no-process-chdir/main.mjs +40 -0
  86. package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
  87. package/rules/test/no-relative-fs-path/concern.json +7 -0
  88. package/rules/test/no-relative-fs-path/docs/index.md +11 -0
  89. package/rules/test/no-relative-fs-path/docs/main.md +34 -0
  90. package/rules/test/no-relative-fs-path/main.mjs +239 -0
  91. package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
  92. package/rules/test/package_json/concern.json +11 -0
  93. package/rules/test/package_json/package_json.mdc +18 -0
  94. package/rules/test/package_json/package_json.rego +25 -0
  95. package/rules/test/package_json/template/package.json.contains.json +6 -0
  96. package/rules/test/sandbox-aware-test/concern.json +7 -0
  97. package/rules/test/sandbox-aware-test/docs/index.md +11 -0
  98. package/rules/test/sandbox-aware-test/docs/main.md +55 -0
  99. package/rules/test/sandbox-aware-test/main.mjs +89 -0
  100. package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
  101. package/rules/test/stryker_config/concern.json +8 -0
  102. package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
  103. package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
  104. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
  105. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
  106. package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
  107. package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
  108. package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
  109. package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
  110. package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
  111. package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
  112. package/rules/test/stryker_config/docs/fix-stryker_config.md +41 -0
  113. package/rules/test/stryker_config/docs/index.md +12 -0
  114. package/rules/test/stryker_config/docs/main.md +38 -0
  115. package/rules/test/stryker_config/fix-stryker_config.mjs +77 -0
  116. package/rules/test/stryker_config/main.mjs +504 -0
  117. package/rules/test/stryker_config/stryker_config.mdc +26 -0
  118. package/rules/test/vitest-api-conventions/concern.json +7 -0
  119. package/rules/test/vitest-api-conventions/docs/index.md +9 -0
  120. package/rules/test/vitest-api-conventions/docs/main.md +40 -0
  121. package/rules/test/vitest-api-conventions/main.mjs +153 -0
  122. package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
  123. package/rules/test/vitest-config-pool-forks/concern.json +7 -0
  124. package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
  125. package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
  126. package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
  127. package/rules/test/vitest-config-pool-forks/vitest-config-pool-forks.mdc +34 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0] - 2026-07-19
4
+
5
+ ### Added
6
+
7
+ - правило test з ядра: розміщення тест-файлів у tests/, ізоляція (no-process-chdir, no-relative-fs-path, no-console-store-restore, sandbox-aware-test, no-bun-test-import), канон vitest/stryker конфігів і vitest-api-конвенції — без cargo-mutants (він у правилі rust плагіна lang-rust)
8
+
9
+ ## [0.6.0] - 2026-07-19
10
+
11
+ ### Added
12
+
13
+ - правило style з ядра (stylelint-детектор css/scss/vue, тулінг-канон, quasar/admin-table/colors/gap-концерни) — фронтенд-сімʼя правил тепер повністю у плагіні
14
+
3
15
  ## [0.5.0] - 2026-07-19
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules-lang-js",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Плагін @7n/rules: JS/npm/bun-екосистема — lint-правила (js/bun/vue/js-run/npm-module/db), taze-провайдер (package.json, bunx taze) і doc-files-екстрактори (oxc AST)",
5
5
  "keywords": [
6
6
  "javascript",
@@ -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,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.vue", "**/*.scss", "**/*.css"]
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/admin_table
4
+ resource: plugins/lang-js/rules/style/admin_table/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,14 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/style/admin_table/main.mjs
5
+ docgen:
6
+ crc: 267496d1
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 '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { walkDir } from '@7n/rules/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,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style
4
+ resource: plugins/lang-js/rules/style/
5
+ ---
6
+
7
+ # npm/rules/style
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.vue", "**/*.scss", "**/*.css"]
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/gap
4
+ resource: plugins/lang-js/rules/style/gap/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,15 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/style/gap/main.mjs
5
+ docgen:
6
+ crc: 23cd7951
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 '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { walkDir } from '@7n/rules/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
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "per-file",
5
+ "glob": ["**/*.{css,scss,vue}"]
6
+ }
7
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-lint.mjs
4
+ resource: plugins/lang-js/rules/style/lint/fix-lint.mjs
5
+ docgen:
6
+ crc: a63dc24c
7
+ ---
8
+
9
+ ## Огляд
10
+
11
+ T0-autofix для `style/lint`: детермінований прогін `stylelint --fix` по css/scss/vue-файлах.
12
+ Виправляє авто-fixable правила stylelint; решта лишається детектору на re-check, далі — LLM-ладдеру.
13
+ Запис незворотний (поза rollback).
14
+
15
+ ## Поведінка
16
+
17
+ - Перелічує цільові файли: у дельта-режимі — зі списку змінених (лише css/scss/vue); у повному —
18
+ tracked css/scss/vue через git.
19
+ - Застосовує `stylelint --fix` на них; до списку змінених потрапляють лише файли з фактичною зміною.
20
+
21
+ ## Публічний API
22
+
23
+ - `patterns` — масив T0-патернів; єдиний `style-stylelint-fix` спрацьовує на агрегованому
24
+ порушенні детектора (`stylelint-violation`).
25
+
26
+ ## Гарантії поведінки
27
+
28
+ - Відсутній stylelint → no-op (без змін).
29
+ - Записуються лише фактично змінені файли; кожен реєструється через `recordWrite`.
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/lint
4
+ resource: plugins/lang-js/rules/style/lint/
5
+ ---
6
+
7
+ # npm/rules/style/lint
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/style/lint/main.mjs
5
+ docgen:
6
+ crc: c9a94f80
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Файл відповідає за вибірку стильових файлів у проекті, що реалізується через функцію `filterStyleFiles`. Він також забезпечує валідацію якості коду в цих файлах шляхом виконання перевірок, що реалізується функцією `lint`.
16
+
17
+ ## Поведінка
18
+
19
+ filterStyleFiles відбирає з наданого списку шляхів лише ті, які мають розширення CSS, SCSS або Vue.
20
+ lint виконує форматування та перевірку стильових файлів у проекті, застосовуючи автоматичне виправлення, якщо це не режим лише для читання.
21
+
22
+ ## Публічний API
23
+
24
+ filterStyleFiles — Відбирає файли стилів для подальшої обробки.
25
+ lint — Перевіряє стилі у файлах (CSS/SCSS/Vue) окремо для кожного або для всього проєкту.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,66 @@
1
+ /** @see ./docs/fix-lint.md */
2
+
3
+ /**
4
+ * T0-autofix для `style/lint` — детермінований прогін `stylelint --fix` по css/scss/vue.
5
+ * Детектор емітить агрегований `stylelint-violation` без переліку файлів, тож фіксер
6
+ * самостійно перелічує цільові файли (ctx.files у дельті або tracked css/scss/vue у повному
7
+ * режимі), форматує їх і повертає лише фактично змінені. Запис permanent. Відсутній
8
+ * stylelint → no-op.
9
+ */
10
+ import { readFileSync } from 'node:fs'
11
+ import { resolve } from 'node:path'
12
+
13
+ import { filterStyleFiles, resolveStylelint } from './main.mjs'
14
+ import { spawnAsync } from '@7n/rules/scripts/utils/spawn-async.mjs'
15
+
16
+ /**
17
+ * Вміст файлу або null, якщо не читається.
18
+ * @param {string} abs абсолютний шлях
19
+ * @returns {string|null} вміст або null
20
+ */
21
+ function readOrNull(abs) {
22
+ try {
23
+ return readFileSync(abs, 'utf8')
24
+ } catch {
25
+ return null
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Перелік цільових style-файлів: у дельті — з ctx.files; у повному режимі — tracked
31
+ * css/scss/vue через `git ls-files`.
32
+ * @param {string} cwd корінь
33
+ * @param {string[]|undefined} ctxFiles файли з контексту (дельта) або undefined (повний режим)
34
+ * @returns {Promise<string[]>} posix-relative шляхи
35
+ */
36
+ async function listStyleFiles(cwd, ctxFiles) {
37
+ if (ctxFiles !== undefined) return filterStyleFiles(ctxFiles)
38
+ const r = await spawnAsync('git', ['ls-files', '-z', '--', '*.css', '*.scss', '*.vue'], { cwd })
39
+ if (r.exitCode !== 0) return []
40
+ return (r.stdout ?? '').split('\0').filter(Boolean)
41
+ }
42
+
43
+ /** @type {import('../../../scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
44
+ export const patterns = [
45
+ {
46
+ id: 'style-stylelint-fix',
47
+ standalone: true, // §8 Phase 2: apply бере ctx.files (не violations), stylelint --fix сам ре-аналізує
48
+ test: violations => violations.some(v => v.reason === 'stylelint-violation'),
49
+ apply: async (violations, ctx) => {
50
+ const stylelint = resolveStylelint(ctx.cwd)
51
+ if (!stylelint) return { touchedFiles: [] }
52
+ const files = await listStyleFiles(ctx.cwd, ctx.files)
53
+ if (files.length === 0) return { touchedFiles: [] }
54
+
55
+ const abs = files.map(f => resolve(ctx.cwd, f))
56
+ const before = new Map(abs.map(a => [a, readOrNull(a)]))
57
+ await spawnAsync(stylelint, ['--fix', ...files], { cwd: ctx.cwd })
58
+
59
+ const touchedFiles = abs.filter(a => readOrNull(a) !== before.get(a))
60
+ for (const a of touchedFiles) ctx.recordWrite?.(a)
61
+ return touchedFiles.length > 0
62
+ ? { touchedFiles, message: `stylelint --fix: ${touchedFiles.length} файл(ів)` }
63
+ : { touchedFiles: [] }
64
+ }
65
+ }
66
+ ]
@@ -0,0 +1,68 @@
1
+ /**
2
+ * lint-поверхня style: read-only detector (stylelint без --fix) для css/scss/vue.
3
+ * Per-file (ctx.files) або весь проєкт (ctx.files === undefined). Якщо stylelint
4
+ * недоступний — крок пропускається (без npx-автовстановлення).
5
+ */
6
+ import { existsSync } from 'node:fs'
7
+ import { join } from 'node:path'
8
+
9
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
10
+ import { resolveCmd } from '@7n/rules/scripts/utils/resolve-cmd.mjs'
11
+ import { spawnAsync } from '@7n/rules/scripts/utils/spawn-async.mjs'
12
+
13
+ const STYLE_EXT_RE = /\.(?:css|scss|vue)$/u
14
+
15
+ /**
16
+ * @param {string[]} files список шляхів
17
+ * @returns {string[]} лише css/scss/vue
18
+ */
19
+ export function filterStyleFiles(files) {
20
+ return files.filter(f => STYLE_EXT_RE.test(f))
21
+ }
22
+
23
+ /**
24
+ * Резолвить бінарник stylelint: спершу локальний node_modules/.bin, потім PATH.
25
+ * @param {string} cwd корінь
26
+ * @returns {string | null} абсолютний шлях або null
27
+ */
28
+ export function resolveStylelint(cwd) {
29
+ const local = join(cwd, 'node_modules', '.bin', 'stylelint')
30
+ if (existsSync(local)) return local
31
+ return resolveCmd('stylelint')
32
+ }
33
+
34
+ /**
35
+ * Detector style/lint (read-only).
36
+ * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd, files)
37
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
38
+ */
39
+ export async function lint(ctx) {
40
+ const reporter = createViolationReporter(ctx)
41
+ const { fail } = reporter
42
+ const cwd = ctx.cwd
43
+ const files = ctx.files
44
+
45
+ /** @type {string[]} */
46
+ const targets = []
47
+ if (files === undefined) {
48
+ targets.push('**/*.{css,scss,vue}')
49
+ } else {
50
+ const style = filterStyleFiles(files)
51
+ if (style.length === 0) return reporter.result()
52
+ targets.push(...style)
53
+ }
54
+
55
+ const stylelint = resolveStylelint(cwd)
56
+ if (!stylelint) {
57
+ // stylelint недоступний → крок пропущено (без автовстановлення)
58
+ return reporter.result()
59
+ }
60
+
61
+ const r = await spawnAsync(stylelint, targets, { cwd, shell: false })
62
+ if (r.exitCode !== 0) {
63
+ const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
64
+ const outSuffix = out ? `\n${out}` : ''
65
+ fail(`lint-style: stylelint — порушення (код ${r.exitCode ?? 1}, style.mdc)${outSuffix}`, 'stylelint-violation')
66
+ }
67
+ return reporter.result()
68
+ }
@@ -0,0 +1 @@
1
+ { "auto": { "glob": ["**/*.css", "**/*.vue"] } }
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: Правила стилів CSS та SCSS
3
+ version: '1.5'
4
+ globs: "**/*.{css,scss,vue}"
5
+ alwaysApply: false
6
+ ---
7
+
8
+ Правило **style** для Vue-проєктів: Quasar як стильова система, SCSS-конвенції кольорів і відступів, фікси компонентів, stylelint через `@nitra/stylelint-config`.
9
+
10
+ - [settings.json.snippet.json](./vscode_settings/template/settings.json.snippet.json)
11
+ - [extensions.json.snippet.json](./vscode_extensions/template/extensions.json.snippet.json)
12
+ - [package.json.snippet.json](./package_json/template/package.json.snippet.json)
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "policy": {
5
+ "files": {
6
+ "single": "package.json"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-package_json.mjs
4
+ resource: plugins/lang-js/rules/style/package_json/fix-package_json.mjs
5
+ docgen:
6
+ crc: f364c0e4
7
+ model: openai-codex/gpt-5.5
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
10
+ ---
11
+
12
+ ## Огляд
13
+
14
+ Надає `patterns` для визначення очікуваних перевірок, що спираються на `package.json`. Використовується як read-only опис вимог до конфігурації пакета, без самостійних змін файлової системи.
15
+
16
+ ## Поведінка
17
+
18
+ 1. `patterns` визначає правило виправлення для `package.json`.
19
+
20
+ 2. `patterns` забезпечує приведення `package.json` до проєктного шаблону стилю.
21
+
22
+ 3. `patterns` не змінює файлову систему самостійно, а лише описує доступний сценарій автоматичного виправлення для зовнішнього механізму перевірок.
23
+
24
+ ## Гарантії поведінки
25
+
26
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/package_json
4
+ resource: plugins/lang-js/rules/style/package_json/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------------------------------- | --------- |
9
+ | [fix-package_json.mjs](fix-package_json.md) | JS Module |
@@ -0,0 +1,3 @@
1
+ import { createTemplateFixPattern } from '@7n/rules/scripts/lib/fix/template-deep-merge.mjs'
2
+
3
+ export const patterns = [createTemplateFixPattern({ id: 'style-package_json-template', targetPath: 'package.json' })]
@@ -0,0 +1,18 @@
1
+ ## package.json: stylelint-конфіг та devDependencies
2
+
3
+ Rego-пакет: `style_lint.package_json`
4
+
5
+ Цільовий файл: `package.json`
6
+
7
+ Дві незалежні перевірки:
8
+
9
+ 1. **snippet-walker** — якщо поле `stylelint` присутнє у `package.json`, перевіряє `stylelint.extends == "@nitra/stylelint-config"` (2-рівневий обхід через template).
10
+ 2. **presence-check** — `@nitra/stylelint-config` має бути у `devDependencies` (ця перевірка завжди активна, не залежить від template).
11
+
12
+ FS-альтернативи (`.stylelintrc.*` файли) та `.stylelintignore` перевіряються у JS, не в Rego.
13
+
14
+ Канонічний template: [package.json.snippet.json](./template/package.json.snippet.json)
15
+
16
+ **✓ Правильно** — `devDependencies` містить `@nitra/stylelint-config`; якщо є поле `stylelint` — `extends` дорівнює `"@nitra/stylelint-config"`.
17
+
18
+ **✗ Неправильно** — відсутній `@nitra/stylelint-config` у `devDependencies`; або поле `stylelint.extends` має інше значення.