@7n/rules 1.42.0 → 1.43.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.43.1] - 2026-07-22
4
+
5
+ ### Fixed
6
+
7
+ - Storybook (правило `storybook`): виправлення хвилі 2a за результатами живого пілота app-скафолда на `gt`. (1) `.storybook/main.js` app-варіанту більше НЕ знімає `vite-plugin-pages` у `viteFinal` — знімання ламало `storybook build` глобально через непідтримуваний `<route lang="yaml">`-блок (`scaffold/template/app-main.js`, `APP_MAIN_JS_MARKERS`). (2) Storybook vitest-проєкт app-пакетів отримує ВЛАСНІ `quasar()`/`AutoImport()`/`Pages()`-плагіни замість успадкованого урізаного unit-конфіга (нові `vitest-config/template/app-storybook-project-entry.js` і `vitest.config.app.baseline.mjs`, type-aware вибір у `fix-vitest-config.mjs`, нові маркер-перевірки в `main.mjs`/`adopt/main.mjs`). (3) `storybook/hygiene` (undeclared-import і sass-variables) тепер перевіряє лише `type: 'library'` пакети — на app-пакетах давав хибні спрацювання на Vite `resolve.alias`-специфікаторах і на свідомо відсутньому `sassVariables`-маркері app-`main.js`. (4) Додано канонічний шаблон `.storybook/vitest.setup.js` (стандартний `@storybook/addon-vitest`-boilerplate) — генерується/перевіряється `scaffold`-концерном для обох типів пакета. (5) `npm/schemas/n-rules.json`: додано `storybook.detectApps`/`storybook.optOut` до кореневої схеми — без цього `additionalProperties: false` відкидав ці вже задокументовані поля `.n-rules.json` як невідомі.
8
+
9
+ ## [1.43.0] - 2026-07-22
10
+
11
+ ### Added
12
+
13
+ - docker: n-rules:bun-no-compile-маркер (# n-rules:bun-no-compile: <причина>) — генералізує native-addon-виняток на будь-яку недосяжну для checker-а причину неможливості bun build --compile (напр. динамічний import() рантайм-конфігу); вимикає вимогу компіляції й дозволяє mirror.gcr.io/oven/bun:* як фінальний stage
14
+
3
15
  ## [1.42.0] - 2026-07-22
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.42.0",
3
+ "version": "1.43.1",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/docker/lint
4
+ resource: npm/rules/docker/lint/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -3,45 +3,49 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/docker/lint/main.mjs
5
5
  docgen:
6
- crc: 26fdea23
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- issues: judge:inaccurate:0.99
6
+ crc: 7cc8c2c7
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 90
10
+ issues: internal-name:checkDockerfile,judge-refine:kept-original,judge:inaccurate:0.99
10
11
  judgeModel: openai-codex/gpt-5.4-mini
11
12
  ---
12
13
 
13
14
  ## Огляд
14
15
 
15
- Please provide the content of the file you want me to document. I need the code or at least the context file to proceed with generating the "Огляд" section based on the provided "Поведінка".
16
+ Файл знаходить Dockerfile і Containerfile в межах репозиторію через `findDockerfilePaths`, визначає stage-структуру через `splitDockerfileStages` і `parseFromStages`, а потім запускає `lint` як fail-safe перевірку без винесення винятків назовні.
17
+ Перевірки спираються на правила з `docker.mdc` і окремо покривають multistage/runtime-узгодженість через `getMultistageAndRuntimeHint`, `getBunCompileHint`, `getNginxAlpineSlimTagHint` і `getNonRootRuntimeHint`, щоб Docker-образи відповідали очікуваній схемі збірки та запуску.
16
18
 
17
19
  ## Поведінка
18
20
 
19
- Поведінка:
20
- isDockerfileName визначає, чи є вказане ім'я файлу Dockerfile або Containerfile.
21
- findDockerfilePaths збирає абсолютні шляхи до всіх Dockerfile / Containerfile, ігноруючи задані шляхи.
22
- parseFromStages витягує всі інструкції `FROM <image>` з вмісту Dockerfile/Containerfile.
23
- splitDockerfileStages розбиває вміст Dockerfile на логічні етапи на основі інструкцій `FROM`.
24
- getMultistageAndRuntimeHint перевіряє, чи відповідає структура Dockerfile вимогам багатоетапної збірки та дозволених runtime-образів (docker.mdc).
25
- getBunCompileHint перевіряє, чи для bun-проєктів на backend runtime виконується необхідна компіляція бінарника та не міститься залишків build tooling.
26
- getNginxAlpineSlimTagHint перевіряє, що для nginx-образів у `FROM` вказано тег `alpine-slim` (docker.mdc).
27
- getNonRootRuntimeHint перевіряє наявність інструкції `USER <non-root>` у фінальному stage (docker.mdc).
28
- main виконує повний перегляд знайдених Dockerfile / Containerfile, застосовуючи перевірки, що спираються на конфіги, зокрема `package.json`, та запускаючи hadolint.
29
- lint є оркестратором, який запускає `main` для перевірки усього репозиторію.
21
+ Detector спочатку знаходить лише Dockerfile і Containerfile у межах репозиторію, відфільтровуючи імена через `isDockerfileName` та враховуючи ignore-маршрути, а далі для кожного файлу запускає послідовну перевірку в межах `lint`.
22
+
23
+ `parseFromStages` і `splitDockerfileStages` дають спільну картину структури файла: перший витягує всі базові образи, другий ділить вміст на stages, щоб наступні перевірки могли оцінювати саме фінальний runtime і build-stage окремо.
24
+
25
+ `getMultistageAndRuntimeHint` використовує ці дані, щоб вимагати multistage і дозволений фінальний runtime відповідно до `docker.mdc`; для bun-runtime робить виняток лише коли є нативний `.node`-аддон або явний `# n-rules:bun-no-compile` маркер. Такий маркер читає `hasBunNoCompileMarker`, і він служить opt-in для випадків, які не можна вивести механічно.
26
+
27
+ `getBunCompileHint` працює лише для bun-проєктів із backend runtime: якщо в образі є `bun install` або `bun i`, але немає compile-кроку в build-stage, або в фінальному stage лишився bun tooling, це вважається порушенням. Вимога спирається на `package.json`, щоб зрозуміти, чи проект справді bun-орієнтований.
28
+
29
+ `getNonRootRuntimeHint` перевіряє, що фінальний runtime не працює як root, а `getNginxAlpineSlimTagHint` звужує окреме правило для nginx-образів до потрібного тегу з `docker.mdc`.
30
+
31
+ Усі ці перевірки збираються в `checkDockerfile`, який для кожного знайденого файла формує violations і додає їх до результату через fail-safe підхід: помилки не виходять назовні, а перетворюються на контрольований lint-результат.
32
+
33
+ `lint` є єдиною точкою запуску для цього detector: вона знаходить файли, проганяє їх через `checkDockerfile` і повертає підсумок для всього репозиторію без запису стану.
30
34
 
31
35
  ## Публічний API
32
36
 
33
- isDockerfileName — визначає, чи є ім'я файлу Dockerfile або Containerfile (включаючи варіації типу `Dockerfile.prod`).
34
- findDockerfilePaths — збирає повні шляхи до файлів Dockerfile або Containerfile, починаючи з поточного каталогу.
35
- parseFromStages — витягує список усіх образів, вказаних у директивах `FROM` у файлі Dockerfile/Containerfile.
36
- splitDockerfileStagesрозділяє Dockerfile на логічні етапи (stages) на основі інструкцій `FROM`.
37
- getMultistageAndRuntimeHintаналізує вимоги до структури багатоетапного збігу: перевіряє, чи є мінімум два етапи з `FROM`, а також чи відповідають образи фінального етапу дозволеним типам (з урахуванням винятків для проєктів з нативними `.node-аддонами`).
38
- getBunCompileHintперевіряє, чи вимагає проєкт на backend runtime, що збірка збігається у бінарний файл.
39
- getNginxAlpineSlimTagHintдля Nginx-образів перевіряє, чи вказано тег `alpine-slim` у відповідному `FROM`, згідно з `docker.mdc`.
40
- getNonRootRuntimeHintперевіряє, чи фінальний етап виконання використовує інструкцію `USER` для роботи від імені не-root користувача, згідно з `docker.mdc`.
41
- mainпроводить лінт-перевірку Dockerfile/Containerfile із використанням hadolint відповідно до `docker.mdc`.
42
- lint — є адаптером, що викликає стандартний лінтер `n-rules lint docker` для обгортання основної логіки.
37
+ - isDockerfileName — Чи є basename Dockerfile / Containerfile (у т.ч. Dockerfile.prod).
38
+ - findDockerfilePaths — Збирає абсолютні шляхи до Dockerfile / Containerfile від кореня cwd.
39
+ - parseFromStages — Витягує всі `FROM <image>` зі вмісту Dockerfile/Containerfile.
40
+ - hasBunNoCompileMarker Явний opt-in консюмера: коментар-рядок `# n-rules:bun-no-compile: <причина>` будь-де у файлі позначає, що `bun build --compile` неможливий з причини поза виявними класами (на відміну від нативних `.node`-аддонів, які виявляються з `package.json#dependencies`).
41
+ - splitDockerfileStages Розбиває Dockerfile на stages за `FROM` (порожній масив, якщо FROM немає).
42
+ - getMultistageAndRuntimeHint Перевіряє multistage (мінімум 2 FROM) і дозволений фінальний runtime-образ (docker.mdc); для нативного `.node`-аддона або `n-rules:bun-no-compile`-маркера додатково дозволяє `mirror.gcr.io/oven/bun:*`.
43
+ - getBunCompileHint Для backend bun-проєкту `bun install`, фінальний FROM — alpine, не frontend, немає `n-rules:bun-no-compile`-маркера) вимагає `bun build --compile` у build stage і відсутність `bun` у фінальному stage.
44
+ - getNginxAlpineSlimTagHint Перевіряє, що для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`) у `FROM` вказано тег `alpine-slim` (docker.mdc).
45
+ - getNonRootRuntimeHint Перевіряє, що у фінальному stage є `USER <name|uid>` і це не `root`/`0` (docker.mdc).
46
+ - lint — Detector docker/lint: Dockerfile/Containerfile mirror/multistage/runtime/non-root + hadolint.
43
47
 
44
48
  ## Гарантії поведінки
45
49
 
46
- - Read-only: не виконує операцій запису (ФС/БД).
50
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
47
51
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -15,6 +15,7 @@ const BUN_INSTALL_RE = /\bbun\s+(?:install|i)\b/iu
15
15
  const BUN_BUILD_COMPILE_RE = /\bbun\s+build\b[^\n]*\s--compile\b/iu
16
16
  const BUN_WORD_RE = /\bbun\b/iu
17
17
  const USER_LINE_RE = /^\s*USER\s+([^\s#]+)/iu
18
+ const BUN_NO_COMPILE_MARKER_RE = /^#\s*n-rules:bun-no-compile:(.*)$/iu
18
19
 
19
20
  const NGINX_UNPRIVILEGED_MIRROR_PREFIX = 'mirror.gcr.io/nginxinc/nginx-unprivileged'
20
21
 
@@ -81,22 +82,39 @@ const RUNTIME_IMAGES = /** @type {const} */ ([
81
82
  /** @type {RegExp} */
82
83
  const DEBIAN_VIA_MIRROR_RE = /^mirror\.gcr\.io\/library\/debian:(.+)$/i
83
84
 
84
- /** Bun-рантайм як фінальний stage — легітимний лише за наявності нативного .node-аддона (див. docker.mdc). */
85
+ /** Bun-рантайм як фінальний stage — легітимний лише за наявності нативного .node-аддона або `n-rules:bun-no-compile`-маркера (див. docker.mdc). */
85
86
  const BUN_RUNTIME_IMAGE = 'mirror.gcr.io/oven/bun'
86
87
 
88
+ /**
89
+ * Явний opt-in консюмера: сервіс не можна пакувати через `bun build --compile` з причини, яку
90
+ * checker не може вивести механічно (динамічний `import()` рантайм-конфігу тощо — на відміну
91
+ * від нативних `.node`-аддонів, які виявляються з `package.json#dependencies`). Маркер —
92
+ * коментар-рядок `# n-rules:bun-no-compile: <причина>` будь-де у файлі; той самий канон, що й для
93
+ * native-addon: ship `node_modules` + `bun <entry>` на `mirror.gcr.io/oven/bun:*`.
94
+ * @param {string} fileContent вміст Dockerfile/Containerfile
95
+ * @returns {boolean} true, якщо маркер присутній із непорожньою причиною
96
+ */
97
+ export function hasBunNoCompileMarker(fileContent) {
98
+ return fileContent.split(NEWLINE_RE).some(line => {
99
+ const m = line.trim().match(BUN_NO_COMPILE_MARKER_RE)
100
+ return Boolean(m && m[1].trim().length > 0)
101
+ })
102
+ }
103
+
87
104
  /**
88
105
  * Чи ref фінального `FROM` відповідає дозволеним у docker.mdc (multistage / runtime).
89
106
  * @param {string} lastLower ref без digest, lower case
90
- * @param {boolean} [hasNativeAddon] чи проєкт залежить від нативного .node-аддона (sharp/@img/argon2)
107
+ * @param {boolean} [allowBunRuntime] чи легітимний bun-рантайм як фінальний stage (нативний аддон або `n-rules:bun-no-compile`-маркер)
91
108
  * @returns {boolean} true, якщо образ дозволений як фінальний runtime
92
109
  */
93
- function isAllowedFinalRuntimeImage(lastLower, hasNativeAddon = false) {
110
+ function isAllowedFinalRuntimeImage(lastLower, allowBunRuntime = false) {
94
111
  if (lastLower === 'scratch' || lastLower.startsWith('scratch:')) {
95
112
  return true
96
113
  }
97
- // Для нативних аддонів канон — ship node_modules + `bun <entry>`, тож фінальний stage на
98
- // mirror.gcr.io/oven/bun:* легітимний (compile неможливий, див. docker-native-addon.mjs).
99
- if (hasNativeAddon && (lastLower === BUN_RUNTIME_IMAGE || lastLower.startsWith(`${BUN_RUNTIME_IMAGE}:`))) {
114
+ // Канон — ship node_modules + `bun <entry>`, тож фінальний stage на mirror.gcr.io/oven/bun:*
115
+ // легітимний, коли compile неможливий (native-addon: docker-native-addon.mjs, або явний
116
+ // n-rules:bun-no-compile-маркер: hasBunNoCompileMarker).
117
+ if (allowBunRuntime && (lastLower === BUN_RUNTIME_IMAGE || lastLower.startsWith(`${BUN_RUNTIME_IMAGE}:`))) {
100
118
  return true
101
119
  }
102
120
  const deb = lastLower.match(DEBIAN_VIA_MIRROR_RE)
@@ -132,7 +150,8 @@ export function splitDockerfileStages(fileContent) {
132
150
  * Перевіряє базові вимоги до структури Dockerfile:
133
151
  * - multistage: мінімум 2 FROM
134
152
  * - фінальний FROM: дозволені образи в docker.mdc (alpine, scratch, debian slim, php, python, nginx, openresty, …);
135
- * для проєктів із нативним .node-аддоном додатково дозволено mirror.gcr.io/oven/bun:* (bun-рантайм)
153
+ * для проєктів із нативним .node-аддоном або `n-rules:bun-no-compile`-маркером додатково дозволено
154
+ * mirror.gcr.io/oven/bun:* (bun-рантайм)
136
155
  * @param {string} fileContent вміст Dockerfile/Containerfile
137
156
  * @param {{ hasNativeAddon?: boolean }} [opts] опції: hasNativeAddon — є нативний .node-аддон (sharp/@img/argon2)
138
157
  * @returns {string | null} повідомлення помилки або null
@@ -148,8 +167,9 @@ export function getMultistageAndRuntimeHint(fileContent, { hasNativeAddon = fals
148
167
  const last = stages.at(-1)
149
168
  const lastImage = (last?.image || '').split('@', 1)[0] || ''
150
169
  const lastLower = lastImage.toLowerCase()
170
+ const allowBunRuntime = hasNativeAddon || hasBunNoCompileMarker(fileContent)
151
171
 
152
- if (!isAllowedFinalRuntimeImage(lastLower, hasNativeAddon)) {
172
+ if (!isAllowedFinalRuntimeImage(lastLower, allowBunRuntime)) {
153
173
  return `фінальний FROM має бути дозволеним runtime-образом (див. docker.mdc: multistage), зараз: ${last?.image} (рядок ${last?.line})`
154
174
  }
155
175
 
@@ -161,7 +181,9 @@ export function getMultistageAndRuntimeHint(fileContent, { hasNativeAddon = fals
161
181
  *
162
182
  * Тригер:
163
183
  * - у Dockerfile є крок `bun install` (або `bun i`);
164
- * - фінальний FROM — `mirror.gcr.io/library/alpine:*` (тобто не nginx/openresty frontend).
184
+ * - фінальний FROM — `mirror.gcr.io/library/alpine:*` (тобто не nginx/openresty frontend);
185
+ * - немає `n-rules:bun-no-compile`-маркера (явний opt-in консюмера — compile неможливий з причини поза
186
+ * виявними класами на кшталт нативних аддонів, напр. динамічний `import()` рантайм-конфігу).
165
187
  *
166
188
  * Очікування:
167
189
  * - у build stage є `bun build --compile`;
@@ -170,6 +192,8 @@ export function getMultistageAndRuntimeHint(fileContent, { hasNativeAddon = fals
170
192
  * @returns {string | null} повідомлення помилки або null
171
193
  */
172
194
  export function getBunCompileHint(fileContent) {
195
+ if (hasBunNoCompileMarker(fileContent)) return null
196
+
173
197
  const stages = splitDockerfileStages(fileContent)
174
198
  if (stages.length === 0) return null
175
199
 
@@ -132,6 +132,30 @@ CMD ["bun", "src/index.js"]
132
132
 
133
133
  Для проєктів **без** нативних аддонів standalone-бінарник на alpine лишається каноном (див. розділ вище про компіляцію).
134
134
 
135
+ ## Виняток: явний `n-rules:bun-no-compile`-маркер (причина поза виявними класами)
136
+
137
+ Нативний `.node`-аддон — механічно виявна причина (є в `package.json#dependencies`). Але бувають причини, які checker вивести не може: наприклад, сервіс завантажує конфіг через динамічний `import()` шляху, невідомого на момент компіляції, — `bun build --compile` такий шлях не трейсить, тож бінарник падає в рантаймі так само, як із нативним аддоном.
138
+
139
+ Для цих випадків — явний opt-in консюмера: коментар-рядок `# n-rules:bun-no-compile: <причина>` будь-де у Dockerfile/Containerfile (причина обов'язкова, непорожня). Маркер вимикає і вимогу `bun build --compile` (розділ «Компіляція bun-проєкту в бінарник»), і заборону `mirror.gcr.io/oven/bun:*` як фінального stage (розділ «Multistage build») — той самий канон, що й для нативних аддонів: ship `node_modules` + `bun <entry>` на `mirror.gcr.io/oven/bun:alpine`.
140
+
141
+ ```dockerfile
142
+ # n-rules:bun-no-compile: gateway.config.js вантажиться через динамічний import(), compile не трейсить його
143
+ FROM mirror.gcr.io/oven/bun:alpine AS build-env
144
+ WORKDIR /app
145
+ COPY package.json .
146
+ RUN bun install --production
147
+ COPY ./src ./src
148
+
149
+ FROM mirror.gcr.io/oven/bun:alpine
150
+ WORKDIR /app
151
+ COPY --from=build-env --chown=bun:bun /app/node_modules ./node_modules
152
+ COPY --from=build-env --chown=bun:bun /app/src ./src
153
+ USER bun
154
+ CMD ["bun", "src/index.js"]
155
+ ```
156
+
157
+ Маркер — **не** заміна нативно-аддонного винятку (той визначається автоматично з `package.json`) і **не** привід уникати компіляції там, де вона можлива — це escape hatch для нового, не закодованого в checker класу причин; використовуй лише коли `bun build --compile` доведено не працює. Перевіряють `hasBunNoCompileMarker` / `getBunCompileHint` / `getMultistageAndRuntimeHint` у **`npm/rules/docker/lint/main.mjs`**.
158
+
135
159
  ## Non-root принцип у фінальному stage
136
160
 
137
161
  Для всіх образів потрібно щоб використовувся non-root принцип. **Спосіб** досягнення non-root залежить від **бази**, а не від зміни ОС — змінювати дистрибутив (Alpine→Debian) заради лише non-root **не треба**. Два шляхи:
@@ -67,6 +67,26 @@
67
67
  "type": "boolean",
68
68
  "description": "Чи синхронізувати `.claude/settings.json` (hooks + permissions, merge зі збереженням користувацьких полів) і slash-команди checks. За замовчуванням true.",
69
69
  "default": true
70
+ },
71
+ "storybook": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "description": "Конфігурація канону Storybook (правило storybook, storybook.mdc).",
75
+ "properties": {
76
+ "detectApps": {
77
+ "type": "boolean",
78
+ "description": "Хвиля 2a: чи додавати у скоуп канону Storybook app-проєкти (vue у dependencies, не бібліотека, + src/pages/) на додачу до Vue component library пакетів хвилі 1. За замовчуванням false.",
79
+ "default": false
80
+ },
81
+ "optOut": {
82
+ "type": "array",
83
+ "description": "Root dir пакетів, виключених зі скоупу канону Storybook (той самий формат, що повертає workspace-роутинг — '.' для кореня, 'packages/ui' тощо).",
84
+ "items": {
85
+ "type": "string",
86
+ "minLength": 1
87
+ }
88
+ }
89
+ }
70
90
  }
71
91
  },
72
92
  "required": [
@@ -115,7 +115,7 @@ export function parseProgramOrNull(content, virtualPath) {
115
115
 
116
116
  /**
117
117
  * Парсить файл і повертає `{ program, comments }` або null. Окремий вхід для перевірок,
118
- * яким потрібні коментарі (наприклад, маркер `// allow-unsafe: ...` біля виклику) —
118
+ * яким потрібні коментарі (наприклад, маркер `// n-rules:allow-unsafe: ...` біля виклику) —
119
119
  * базовий `parseProgramOrNull` свідомо лишається без коментарів, щоб не змінювати API.
120
120
  * @param {string} content вихідний код
121
121
  * @param {string} virtualPath шлях для вибору `lang` (також для діагностики)
@@ -3,52 +3,53 @@ type: JS Module
3
3
  title: ast-scan-utils.mjs
4
4
  resource: npm/scripts/utils/ast-scan-utils.mjs
5
5
  docgen:
6
- crc: ed9f189b
6
+ crc: 0a015cfc
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge:error
11
+ judgeModel: openai-codex/gpt-5.4-mini
7
12
  ---
8
13
 
9
- Цей файл містить утиліти для AST-сканерів JavaScript та TypeScript, що використовуються для аналізу коду та виявлення потенційних проблем. Він надає інструменти для обробки AST, перетворення даних та взаємодії з різними типами вузлів, забезпечуючи основу для створення правил безпеки та аналізу коду. Ці утиліти спрощують розробку сканерів, усуваючи необхідність повторного написання boilerplate-коду.
14
+ ## Огляд
15
+
16
+ Утиліти для AST-сканерів JS/TS на `oxc-parser`: `langFromPath` вибирає мову за шляхом файлу, `offsetToLine` переводить зміщення в номер рядка, `normalizeSnippet` стискає фрагмент коду, а `parseProgramOrNull` і `parseProgramAndCommentsOrNull` безпечно повертають `null` замість винятку, коли розбір не вдався.
17
+
18
+ Файл також надає спільні засоби для аналізу дерева й контексту: `walkAstWithAncestors` обходить AST разом із предками, `isFunctionNode` і `isJoinCall` розпізнають типові вузли, `templateQuasisText` та `isSqlListContextTemplate` допомагають працювати з `TemplateLiteral`, а `requireCallModule` і `dynamicImportModule` виділяють модуль із викликів імпорту.
10
19
 
11
20
  ## Поведінка
12
21
 
13
- langFromPath: Визначає мову (js, jsx, ts, tsx) на основі розширення файлу.
14
- offsetToLine: Перетворює байтове зміщення в номер рядка для текстового файлу.
15
- normalizeSnippet: Стискає текстовий фрагмент до 180 символів, видаляючи пробіли.
16
- isFunctionNode: Визначає, чи є вузол AST функцією (FunctionDeclaration, FunctionExpression, ArrowFunctionExpression).
17
- walkAstWithAncestors: Рекурсивно обходить AST, збираючи предки вузла.
18
- parseProgramOrNull: Парсує файл JS/TS та повертає програму або null, якщо є помилки.
19
- parseProgramAndCommentsOrNull: Парсує файл JS/TS та повертає програму та список коментарів, або null, якщо є помилки.
20
- isJoinCall: Визначає, чи є виклик `join` у TemplateLiteral.
21
- templateQuasisText: Збирає текст quasis з TemplateLiteral.
22
- isSqlListContextTemplate: Визначає, чи є TemplateLiteral контекстом SQL-списку (IN/VALUES).
23
- requireCallModule: Витягує ім'я модуля з аргументу виклику `require`.
24
- dynamicImportModule: Витягує ім'я модуля з аргументу виклику `import`.
22
+ Утиліти працюють як спільний шар для AST-сканерів: спочатку за шляхом файлу визначається мова парсингу, далі текст розбирається в `program`, а для перевірок, яким потрібні коментарі поруч із кодом, — у пару `program` + `comments`. Якщо розбір не вдається, зовнішнім споживачам повертається `null`, щоб сканування не падало на синтаксично проблемних файлах.
23
+
24
+ Обхід дерева будується з урахуванням предків, щоб правила могли відрізняти контекст верхнього рівня від вкладеного всередині функцій. Під час такого обходу вузли-функції та виклики спискових операцій розпізнаються як типові шаблони для аналізу, а текстові фрагменти нормалізуються до компактного вигляду для повідомлень про порушення.
25
+
26
+ Окремий набір хелперів працює з `TemplateLiteral`: один збирає видимий текст усіх частин без вставок, інший за цим текстом визначає, чи схоже місце на SQL-контекст зі списком значень. Це дає змогу знаходити небезпечні або підозрілі шаблони без дублювання однакової логіки в різних правилах.
27
+
28
+ Для аналізу імпортів спільно використовуються перевірки на звичайний `require` і динамічний `import` з рядковим модулем. Обидва хелпери повертають лише назву модуля або порожній результат, щоб сканери могли однаково працювати з різними формами завантаження без прив’язки до конкретного правила.
29
+
30
+ Усі операції побудовані fail-safe: помилки парсингу або несподівані вузли не пробиваються назовні, а переводяться в безпечний результат. Це дозволяє сканерам пропускати проблемні фрагменти й продовжувати перевірку решти коду.
25
31
 
26
32
  ## Публічний API
27
33
 
28
- - langFromPath — Визначає мову Oxc на основі розширення файлу.
29
- - offsetToLine — Перетворює зміщення в буфер на номер рядка.
30
- - normalizeSnippet — Форматує текст повідомлення про порушення, видаляючи зайві пробіли.
31
- - isFunctionNode — Визначає, чи є вузол у абстрактному синтаксичному дереві (AST) функцією.
32
- - walkAstWithAncestors — Рекурсивно обходить AST, враховуючи контекст (чи знаходиться вузол всередині функції).
33
- - parseProgramOrNull — Парсить файл та повертає AST, якщо успішно, або `null` у разі помилки.
34
- - parseProgramAndCommentsOrNull — Парсить файл та повертає об'єкт з AST та коментарями, або `null` у разі помилки.
35
- - isJoinCall Визначає, чи є виклик `.join` (для динамічних списків SQL).
36
- - templateQuasisText Витягує текст з `quasis` у `TemplateLiteral`, ігноруючи вирази.
37
- - isSqlListContextTemplateВизначає, чи є `TemplateLiteral` контекстом SQL-списку (наприклад, `IN` або `VALUES`).
38
- - requireCallModuleПеревіряє, чи є виклик `require` з рядковим ім'ям.
34
+ - langFromPath — Мова для Oxc за шляхом файлу (розширення).
35
+ - offsetToLine — Номер рядка (1-based) за зміщенням у буфері.
36
+ - normalizeSnippet — Стискає пробіли для повідомлення про порушення.
37
+ - isFunctionNode — Чи є вузол функцією.
38
+ - walkAstWithAncestors — Рекурсивний обхід AST з предками, щоб визначати контекст (всередині функції чи ні).
39
+ - parseProgramOrNull — Парсить файл і повертає `program` або null, якщо є синтаксичні помилки чи виняток.
40
+ - parseProgramAndCommentsOrNull — Парсить файл і повертає `{ program, comments }` або null. Окремий вхід для перевірок,
41
+ яким потрібні коментарі (наприклад, маркер `// n-rules:allow-unsafe: ...` біля виклику)
42
+ базовий `parseProgramOrNull` свідомо лишається без коментарів, щоб не змінювати API.
43
+ - isJoinCallЧи це `.join(...)` виклик (типово для динамічних списків у SQL).
44
+ - templateQuasisTextТекст quasis у TemplateLiteral (без expressions).
45
+ - isSqlListContextTemplate — Чи виглядає TemplateLiteral як SQL-контекст зі списком (IN/VALUES (...)).
46
+ - requireCallModule — Перевіряє, чи це виклик `require('<module>')` з рядковим аргументом.
47
+ Спільне для сканерів імпортів (`bunyan-imports`, `redis-imports`, ...).
48
+ - dynamicImportModule — Перевіряє, чи це динамічний `import('<module>')` з рядковим аргументом.
49
+ Спільне для сканерів імпортів.
39
50
 
40
51
  ## Гарантії поведінки
41
52
 
42
- - `langFromPath` повертає назву мови JavaScript або TypeScript на основі розширення файлу.
43
- - `langFromPath` повертає `null`, якщо розширення файлу не підтримується.
44
- - `offsetToLine` перетворює зміщення в коді на номер рядка.
45
- - `offsetToLine` повертає `null`, якщо зміщення недійсне.
46
- - `normalizeSnippet` стискає текст сніпета.
47
- - `normalizeSnippet` повертає `null`, якщо не вдалося стиснути сніпет.
48
- - `isFunctionNode` визначає, чи є вузол AST функцією.
49
- - `isFunctionNode` повертає `true` якщо вузол є функцією, інакше `false`.
50
- - `walkAstWithAncestors` обходить AST, враховуючи предки вузлів.
51
- - `walkAstWithAncestors` не повертає значень.
52
- - `parseProgramOrNull` парсує програму та повертає її як AST або `null`, якщо парсинг не вдається.
53
- - `parseProgramOrNull` повертає `null`, якщо програма не може бути успішно розпарсена.
54
- - `parseProgramAndCommentsOrNull` парсує програму
53
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
54
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
55
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.