@nitra/cursor 1.8.160 → 1.8.163
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 +21 -0
- package/mdc/js-bun-db.mdc +29 -1
- package/mdc/js-lint.mdc +1 -1
- package/mdc/text.mdc +2 -2
- package/package.json +4 -1
- package/scripts/check-js-bun-db.mjs +23 -7
- package/scripts/check-js-lint.mjs +29 -0
- package/scripts/check-text.mjs +3 -3
- package/scripts/utils/bun-sql-scan.mjs +69 -9
- package/scripts/utils/oxlint-canonical-skeleton.json +1 -1
- package/scripts/utils/oxlint-canonical.json +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.8.163] - 2026-05-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `check-js-lint.mjs`: `ignorePatterns` у `.oxlintrc.json` тепер звіряється як `rules` — канонічні патерни мають бути присутні, додаткові локальні glob-и дозволені (раніше була строга рівність — будь-який зайвий запис зламував перевірку).
|
|
12
|
+
- `check-text.mjs`: `OXFMT_REQUIRED_IGNORE_PATTERNS` доповнено `**/auto-imports.d.ts` (узгоджено з каноном oxlint); перевірка `.oxfmtrc.json` уже працює як subset, тому локальні розширення не падають.
|
|
13
|
+
- `js-lint.mdc` / `n-js-lint.mdc`, `text.mdc` / `n-text.mdc`: документовано, що канон задає мінімум `ignorePatterns`, локальне розширення дозволене.
|
|
14
|
+
|
|
15
|
+
## [1.8.162] - 2026-05-01
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `oxlint-canonical-skeleton.json` (та перебудований `oxlint-canonical.json`): `ignorePatterns` тепер містить `["**/schema.graphql", "**/auto-imports.d.ts"]` (узгоджено з `.oxfmtrc.json`). Споживачі мають синхронізувати корінь `.oxlintrc.json` із каноном — `check-js-lint` падатиме, поки масив не збігається.
|
|
20
|
+
|
|
21
|
+
## [1.8.161] - 2026-05-01
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `js-bun-db.mdc` (v1.5): нова секція «Прибирати pg-leftover виклики (`.connect()`, `.end()`)». У файлах з Bun SQL прапоруються `<obj>.connect(...)` і `<obj>.end(...)` як ручний lifecycle, який Bun SQL робить за тебе. Opt-out — маркер `// allow-pg-leftover: <причина>` (line- або block-коментар на тому ж рядку чи безпосередньо перед викликом).
|
|
26
|
+
- `bun-sql-scan.mjs`: новий сканер `findBunSqlPgLeftoverCallInText` (скоп — лише файли з `import { sql|SQL } from 'bun'`, щоб не давати false-positive на WebSocket/Stream `.end()`). Виділено спільний `hasMarkerCommentNear` для обох opt-in маркерів (`allow-unsafe`, `allow-pg-leftover`).
|
|
27
|
+
|
|
7
28
|
## [1.8.160] - 2026-05-01
|
|
8
29
|
|
|
9
30
|
### Changed
|
package/mdc/js-bun-db.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Використання pg / mysql2 / Bun SQL у Node.js та Bun
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.5'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Підтримувані версії баз даних
|
|
@@ -151,6 +151,34 @@ await sql`SELECT * FROM users WHERE id IN (${ids.join(',')})`
|
|
|
151
151
|
|
|
152
152
|
Для динамічних списків — `sql([...])` або `sql(rows, 'colA', 'colB')`, **не** `.join(',')`.
|
|
153
153
|
|
|
154
|
+
## Прибирати pg-leftover виклики (`.connect()`, `.end()`)
|
|
155
|
+
|
|
156
|
+
У файлах з Bun SQL (`import { sql, SQL } from 'bun'`) залишки від `pg` — `pool.connect()`, `client.end()`, `pool.end()` — мають бути видалені. Bun SQL пулом керує сам: на першому запиті підключається, idle/lifetime закриває за конфігом — окремий життєвий цикл вручну не потрібен.
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// ❌ pg-leftover: ручний lifecycle, який Bun SQL робить за тебе
|
|
160
|
+
const client = await pool.connect()
|
|
161
|
+
try {
|
|
162
|
+
await client.query('...')
|
|
163
|
+
} finally {
|
|
164
|
+
await client.end()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ✅ Bun SQL — без явних .connect()/.end()
|
|
168
|
+
await sql`...`
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Якщо виклик дійсно потрібен (наприклад, `sql.end()` у graceful shutdown або `.connect()` на сторонньому об'єкті, що випадково ділить імʼя методу), додай маркер `// allow-pg-leftover: <причина>` на тому ж рядку (trailing) або на рядку безпосередньо перед викликом:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// allow-pg-leftover: graceful shutdown — закриваємо пул перед exit
|
|
175
|
+
await sql.end()
|
|
176
|
+
|
|
177
|
+
ws.connect(url) // allow-pg-leftover: WebSocket, не pg
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Формат маркера: `allow-pg-leftover: <непорожня причина>` у line- або block-коментарі. Без маркера й без причини — **fail** перевірки.
|
|
181
|
+
|
|
154
182
|
## Що НЕ робити
|
|
155
183
|
|
|
156
184
|
### Не створювати підключення на кожен запит
|
package/mdc/js-lint.mdc
CHANGED
|
@@ -30,7 +30,7 @@ version: '1.16'
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
У корені має бути **`.oxlintrc.json`**, який **збігається з каноном** oxlint з пакета **`@nitra/cursor`**: файл **`npm/scripts/utils/oxlint-canonical.json`** (plugins, jsPlugins з **`@e18e/eslint-plugin`**, categories, повний набір **rules** із канону — додаткові записи в **`rules`** дозволені; також **`settings`**, **`env`**, **`globals
|
|
33
|
+
У корені має бути **`.oxlintrc.json`**, який **збігається з каноном** oxlint з пакета **`@nitra/cursor`**: файл **`npm/scripts/utils/oxlint-canonical.json`** (plugins, jsPlugins з **`@e18e/eslint-plugin`**, categories, повний набір **rules** із канону — додаткові записи в **`rules`** дозволені; також **`settings`**, **`env`**, **`globals`**). Поле **`ignorePatterns`** працює як **`rules`**: канонічні патерни з **`oxlint-canonical.json`** (наразі **`**/schema.graphql`**, **`**/auto-imports.d.ts`**) мають бути присутні, додаткові локальні glob-и дозволені. Оновити канон можна з репозиторію пакета або скопіювавши файл після **`bun ./scripts/utils/rebuild-oxlint-canonical.mjs`** (джерело правил — **`oxlint-rules.tsv`** + скелет **`oxlint-canonical-skeleton.json`**). Модуль **`@e18e/eslint-plugin`** не оголошуй окремо в **`package.json`** — він уже в залежностях **`@nitra/eslint-config`** (з **3.8.0**), oxlint підвантажує його з **`node_modules`**.
|
|
34
34
|
|
|
35
35
|
Мінімум для розуміння структури (реальний корінь конфігу має збігатися з каноном повністю):
|
|
36
36
|
|
package/mdc/text.mdc
CHANGED
|
@@ -82,7 +82,7 @@ version: '1.25'
|
|
|
82
82
|
|
|
83
83
|
```json title=".oxfmtrc.json"
|
|
84
84
|
{
|
|
85
|
-
"ignorePatterns": ["**/hasura/metadata/**", "**/schema.graphql"],
|
|
85
|
+
"ignorePatterns": ["**/hasura/metadata/**", "**/schema.graphql", "**/auto-imports.d.ts"],
|
|
86
86
|
"arrowParens": "avoid",
|
|
87
87
|
"printWidth": 120,
|
|
88
88
|
"bracketSpacing": true,
|
|
@@ -104,7 +104,7 @@ version: '1.25'
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
Поле **`ignorePatterns`** обовʼязкове: у масиві мають бути **`**/hasura/metadata
|
|
107
|
+
Поле **`ignorePatterns`** обовʼязкове: у масиві мають бути **`**/hasura/metadata/**`**, **`**/schema.graphql`** і **`**/auto-imports.d.ts`**; інші glob-и додавай за потреби (згенеровані каталоги тощо) — канон задає мінімум, локальні розширення дозволені.
|
|
108
108
|
|
|
109
109
|
Також потрібно прибрати, якщо є в проєкті, модуль **`@nitra/prettier-config`**, **prettier** та всі виклики prettier і налаштування для нього.
|
|
110
110
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.163",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -47,5 +47,8 @@
|
|
|
47
47
|
"engines": {
|
|
48
48
|
"bun": ">=1.3",
|
|
49
49
|
"node": ">=25"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@nitra/cursor": "^1.8.163"
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* допустимий лише для підстановки назви таблиці/колонки чи dynamic SQL/DDL,
|
|
16
16
|
* коли значення контролюється кодом (не user input) — в інших випадках
|
|
17
17
|
* переробляємо на tagged template `sql\`...\${value}...\``.
|
|
18
|
+
* - pg-leftover виклики `<obj>.connect(...)` / `<obj>.end(...)` у файлах, що
|
|
19
|
+
* імпортують Bun SQL: пулом керує Bun, життєвий цикл вручну не потрібен.
|
|
20
|
+
* Opt-out — маркер `// allow-pg-leftover: <reason>`.
|
|
18
21
|
* - Динамічні SQL-списки через `.join(',')` у `IN (...)` / `VALUES (...)`
|
|
19
22
|
* (треба `sql([...])`).
|
|
20
23
|
*/
|
|
@@ -25,6 +28,7 @@ import { join, relative, sep } from 'node:path'
|
|
|
25
28
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
26
29
|
import {
|
|
27
30
|
findBunSqlPerRequestConnectionInText,
|
|
31
|
+
findBunSqlPgLeftoverCallInText,
|
|
28
32
|
findBunSqlUnsafeUseWithoutAllowMarkerInText,
|
|
29
33
|
findUnsafeBunSqlDynamicSqlListInText,
|
|
30
34
|
findUnsafeBunSqlInListMissingEmptyGuardInText,
|
|
@@ -128,7 +132,7 @@ async function checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter) {
|
|
|
128
132
|
*/
|
|
129
133
|
async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
|
|
130
134
|
const { fail } = reporter
|
|
131
|
-
const counts = { perRequest: 0, unsafeCall: 0, dynamicList: 0, inListGuard: 0 }
|
|
135
|
+
const counts = { perRequest: 0, unsafeCall: 0, dynamicList: 0, inListGuard: 0, pgLeftover: 0 }
|
|
132
136
|
let hasBunSqlImport = false
|
|
133
137
|
|
|
134
138
|
for (const absPath of sourcePaths) {
|
|
@@ -148,7 +152,7 @@ async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
|
|
|
148
152
|
* @param {string} content вміст файлу
|
|
149
153
|
* @param {string} rel posix-шлях відносно `repoRoot`
|
|
150
154
|
* @param {(msg: string) => void} fail callback при помилці
|
|
151
|
-
* @param {{ perRequest: number, unsafeCall: number, dynamicList: number, inListGuard: number }} counts акумулятори
|
|
155
|
+
* @param {{ perRequest: number, unsafeCall: number, dynamicList: number, inListGuard: number, pgLeftover: number }} counts акумулятори
|
|
152
156
|
* @returns {void}
|
|
153
157
|
*/
|
|
154
158
|
function scanFileForBunSqlPatterns(content, rel, fail, counts) {
|
|
@@ -169,6 +173,15 @@ function scanFileForBunSqlPatterns(content, rel, fail, counts) {
|
|
|
169
173
|
`(js-bun-db.mdc): ${v.snippet}`
|
|
170
174
|
)
|
|
171
175
|
}
|
|
176
|
+
for (const v of findBunSqlPgLeftoverCallInText(content, rel)) {
|
|
177
|
+
counts.pgLeftover++
|
|
178
|
+
fail(
|
|
179
|
+
`js-bun-db: ${rel}:${v.line} — pg-leftover виклик .${v.methodName}(...): Bun SQL пулом керує сам, ` +
|
|
180
|
+
`видали зайвий .connect()/.end() або, якщо випадок легітимний (graceful shutdown тощо), ` +
|
|
181
|
+
`додай маркер "// allow-pg-leftover: <причина>" на тому ж рядку або рядком вище ` +
|
|
182
|
+
`(js-bun-db.mdc): ${v.snippet}`
|
|
183
|
+
)
|
|
184
|
+
}
|
|
172
185
|
for (const v of findUnsafeBunSqlDynamicSqlListInText(content, rel)) {
|
|
173
186
|
counts.dynamicList++
|
|
174
187
|
fail(
|
|
@@ -237,11 +250,8 @@ export async function check() {
|
|
|
237
250
|
return reporter.getExitCode()
|
|
238
251
|
}
|
|
239
252
|
|
|
240
|
-
const { hasBunSqlImport, perRequest, unsafeCall, dynamicList, inListGuard } =
|
|
241
|
-
sourcePaths,
|
|
242
|
-
repoRoot,
|
|
243
|
-
reporter
|
|
244
|
-
)
|
|
253
|
+
const { hasBunSqlImport, perRequest, unsafeCall, dynamicList, inListGuard, pgLeftover } =
|
|
254
|
+
await scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter)
|
|
245
255
|
|
|
246
256
|
if (!hasBunSqlImport) {
|
|
247
257
|
pass("js-bun-db: Bun SQL не використовується в коді (немає import { sql|SQL } from 'bun')")
|
|
@@ -254,6 +264,12 @@ export async function check() {
|
|
|
254
264
|
if (unsafeCall === 0) {
|
|
255
265
|
pass('js-bun-db: усі sql.unsafe(...) або відсутні, або супроводжуються маркером "// allow-unsafe: <причина>"')
|
|
256
266
|
}
|
|
267
|
+
if (pgLeftover === 0) {
|
|
268
|
+
pass(
|
|
269
|
+
'js-bun-db: немає pg-leftover викликів .connect()/.end() у файлах з Bun SQL ' +
|
|
270
|
+
'(або всі вони мають маркер "// allow-pg-leftover: <причина>")'
|
|
271
|
+
)
|
|
272
|
+
}
|
|
257
273
|
if (dynamicList === 0) {
|
|
258
274
|
pass("js-bun-db: немає небезпечних динамічних SQL-списків через .join(',') у IN/VALUES")
|
|
259
275
|
}
|
|
@@ -133,6 +133,30 @@ function compareOxlintRules(expected, actual, failures) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Звіряє блок `ignorePatterns`: кожен патерн із канону має бути присутній в actual; додаткові локальні
|
|
138
|
+
* патерни дозволені (канон задає мінімум, проєкт може розширити).
|
|
139
|
+
* @param {unknown} expected канонічний масив `ignorePatterns`
|
|
140
|
+
* @param {unknown} actual поточний `ignorePatterns` із `.oxlintrc.json`
|
|
141
|
+
* @param {string[]} failures буфер для помилок
|
|
142
|
+
*/
|
|
143
|
+
function compareOxlintIgnorePatterns(expected, actual, failures) {
|
|
144
|
+
if (!Array.isArray(expected)) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(actual)) {
|
|
148
|
+
failures.push('.oxlintrc.json: поле "ignorePatterns" має бути масивом (канон задає мінімум, додаткові патерни дозволені)')
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
const set = new Set(actual)
|
|
152
|
+
const missing = expected.filter(p => !set.has(p))
|
|
153
|
+
if (missing.length > 0) {
|
|
154
|
+
failures.push(
|
|
155
|
+
`.oxlintrc.json: ignorePatterns має містити канонічні патерни — додай: ${missing.map(p => JSON.stringify(p)).join(', ')}`
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
136
160
|
/**
|
|
137
161
|
* Перевіряє `.oxlintrc.json` проти канону пакета `@nitra/cursor` (усі правила з канону та інші поля з `oxlint-canonical.json`).
|
|
138
162
|
* Додаткові ключі лише в `rules` дозволені; інші поля мають збігатися з каноном.
|
|
@@ -160,6 +184,11 @@ export function verifyOxlintRcAgainstCanonical(cfg, canonical) {
|
|
|
160
184
|
continue
|
|
161
185
|
}
|
|
162
186
|
|
|
187
|
+
if (key === 'ignorePatterns') {
|
|
188
|
+
compareOxlintIgnorePatterns(expected, actual, failures)
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
|
|
163
192
|
if (!deepEqualOxlintCanonical(actual, expected)) {
|
|
164
193
|
failures.push(
|
|
165
194
|
`.oxlintrc.json: поле "${key}" має збігатися з каноном пакета @nitra/cursor (npm/scripts/utils/oxlint-canonical.json)`
|
package/scripts/check-text.mjs
CHANGED
|
@@ -29,8 +29,8 @@ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/
|
|
|
29
29
|
/** Заголовок абзацу про апостроф у text.mdc / n-text.mdc. */
|
|
30
30
|
const UK_APOSTROPHE_HEADING = '**Український апостроф:**'
|
|
31
31
|
|
|
32
|
-
/** Мінімальні glob-и в `ignorePatterns` у `.oxfmtrc.json` (text.mdc)
|
|
33
|
-
const OXFMT_REQUIRED_IGNORE_PATTERNS = ['**/hasura/metadata/**', '**/schema.graphql']
|
|
32
|
+
/** Мінімальні glob-и в `ignorePatterns` у `.oxfmtrc.json` (text.mdc) — додаткові патерни локально дозволені. */
|
|
33
|
+
const OXFMT_REQUIRED_IGNORE_PATTERNS = ['**/hasura/metadata/**', '**/schema.graphql', '**/auto-imports.d.ts']
|
|
34
34
|
|
|
35
35
|
/** Канонічні записи `ignorePaths` у `.cspell.json` (text.mdc) — кожен має бути присутнім. */
|
|
36
36
|
const CSPELL_REQUIRED_IGNORE_PATHS = [
|
|
@@ -211,7 +211,7 @@ async function checkOxfmtRc(passFn, failFn) {
|
|
|
211
211
|
const set = new Set(cfg.ignorePatterns)
|
|
212
212
|
const missingPatterns = OXFMT_REQUIRED_IGNORE_PATTERNS.filter(p => !set.has(p))
|
|
213
213
|
if (missingPatterns.length === 0) {
|
|
214
|
-
passFn('.oxfmtrc.json: ignorePatterns містить hasura/metadata
|
|
214
|
+
passFn('.oxfmtrc.json: ignorePatterns містить hasura/metadata, schema.graphql і auto-imports.d.ts')
|
|
215
215
|
} else {
|
|
216
216
|
failFn(
|
|
217
217
|
`.oxfmtrc.json ignorePatterns: додай відсутні елементи: ${missingPatterns.join(', ')} (канонічний приклад у text.mdc)`
|
|
@@ -34,6 +34,14 @@ const IN_PLACEHOLDER_END_RE = /\bin\s*(\(\s*)?$/iu
|
|
|
34
34
|
// `// allow-unsafe: <reason>` — `allow-unsafe`, двокрапка, **непорожня** причина.
|
|
35
35
|
// Без причини маркер не приймається: ціль — лишити слід для ревʼюера, а не «німий» прапорець.
|
|
36
36
|
const ALLOW_UNSAFE_MARKER_RE = /\ballow-unsafe\s*:\s*\S+/u
|
|
37
|
+
// `// allow-pg-leftover: <reason>` — opt-in для виправданих `.connect()` / `.end()`
|
|
38
|
+
// у файлах з Bun SQL (наприклад, `sql.end()` у graceful shutdown або `.connect()`
|
|
39
|
+
// на сторонньому об'єкті, що випадково ділить імʼя методу з `pg`).
|
|
40
|
+
const ALLOW_PG_LEFTOVER_MARKER_RE = /\ballow-pg-leftover\s*:\s*\S+/u
|
|
41
|
+
// pg-API, які не потрібні з Bun SQL: pool/client життєвий цикл вручну.
|
|
42
|
+
// `release` не входить — Bun SQL такого методу не має, а `.connect()` / `.end()`
|
|
43
|
+
// формально існують і там, тому опт-аут маркером лишається доречним.
|
|
44
|
+
const PG_LEFTOVER_METHOD_NAMES = new Set(['connect', 'end'])
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
47
|
* @param {unknown} node AST node
|
|
@@ -214,24 +222,26 @@ function isUnsafeCall(node) {
|
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
/**
|
|
217
|
-
* Чи є біля виклику
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* відірваний коментар через порожній рядок не зараховується — щоб маркер
|
|
225
|
+
* Чи є біля виклику маркер-коментар, що матчиться на `markerRe`, на тому ж рядку,
|
|
226
|
+
* що й початок виклику, або на рядку, що передує початку виклику. Це навмисно строга
|
|
227
|
+
* суміжність: відірваний коментар через порожній рядок не зараховується — щоб маркер
|
|
221
228
|
* стояв саме біля виклику, а не «загубився десь вище».
|
|
222
|
-
*
|
|
229
|
+
*
|
|
230
|
+
* Використовується для opt-in маркерів типу `// allow-unsafe: ...` і `// allow-pg-leftover: ...`.
|
|
231
|
+
* @param {{ start: number }} callNode виклик
|
|
223
232
|
* @param {{ type: 'Line' | 'Block', value: string, start: number, end: number }[]} comments коментарі з парсера
|
|
224
233
|
* @param {string} content вихідний код
|
|
234
|
+
* @param {RegExp} markerRe регекс, що валідує текст маркера в `comment.value`
|
|
225
235
|
* @returns {boolean} true, якщо маркер знайдено
|
|
226
236
|
*/
|
|
227
|
-
function
|
|
237
|
+
function hasMarkerCommentNear(callNode, comments, content, markerRe) {
|
|
228
238
|
const callStartLine = offsetToLine(content, callNode.start)
|
|
229
239
|
for (const c of comments) {
|
|
230
240
|
if (!c || (c.type !== 'Line' && c.type !== 'Block')) continue
|
|
231
|
-
if (typeof c.value !== 'string' || !
|
|
241
|
+
if (typeof c.value !== 'string' || !markerRe.test(c.value)) continue
|
|
232
242
|
const startLine = offsetToLine(content, c.start)
|
|
233
243
|
const endLine = offsetToLine(content, c.end)
|
|
234
|
-
// trailing-коментар на тому ж рядку (
|
|
244
|
+
// trailing-коментар на тому ж рядку (`<call> // marker: ...`)
|
|
235
245
|
if (startLine === callStartLine) return true
|
|
236
246
|
// коментар на рядку, що безпосередньо передує виклику — для блокових
|
|
237
247
|
// коментарів важливим є саме `endLine`, бо block може займати кілька рядків.
|
|
@@ -240,6 +250,23 @@ function hasAllowUnsafeMarkerNear(callNode, comments, content) {
|
|
|
240
250
|
return false
|
|
241
251
|
}
|
|
242
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Чи це pg-leftover виклик: `<obj>.connect(...)` або `<obj>.end(...)`. Bun SQL пулом
|
|
255
|
+
* керує сам — і `.connect()`, і `.end()` у файлах з Bun SQL зазвичай зайві, тож такі
|
|
256
|
+
* виклики прапоруються (з opt-in маркером для рідкісних легітимних випадків).
|
|
257
|
+
* @param {unknown} node AST node
|
|
258
|
+
* @returns {{ name: 'connect' | 'end' } | null} ім'я pg-leftover методу або null
|
|
259
|
+
*/
|
|
260
|
+
function asPgLeftoverCall(node) {
|
|
261
|
+
if (!node || node.type !== 'CallExpression') return null
|
|
262
|
+
const callee = node.callee
|
|
263
|
+
if (!callee || callee.type !== 'MemberExpression' || callee.computed) return null
|
|
264
|
+
const prop = callee.property
|
|
265
|
+
if (!prop || prop.type !== 'Identifier' || typeof prop.name !== 'string') return null
|
|
266
|
+
if (!PG_LEFTOVER_METHOD_NAMES.has(prop.name)) return null
|
|
267
|
+
return { name: /** @type {'connect' | 'end'} */ (prop.name) }
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
/**
|
|
244
271
|
* Знаходить `new SQL(...)` всередині функцій (handler на кожен запит замість singleton).
|
|
245
272
|
* @param {string} content вихідний код
|
|
@@ -286,7 +313,7 @@ export function findBunSqlUnsafeUseWithoutAllowMarkerInText(content, virtualPath
|
|
|
286
313
|
const out = []
|
|
287
314
|
walkAstWithAncestors(program, [], node => {
|
|
288
315
|
if (!isUnsafeCall(node)) return
|
|
289
|
-
if (
|
|
316
|
+
if (hasMarkerCommentNear(node, comments, content, ALLOW_UNSAFE_MARKER_RE)) return
|
|
290
317
|
out.push({
|
|
291
318
|
line: offsetToLine(content, node.start),
|
|
292
319
|
snippet: normalizeSnippet(content.slice(node.start, node.end))
|
|
@@ -295,6 +322,39 @@ export function findBunSqlUnsafeUseWithoutAllowMarkerInText(content, virtualPath
|
|
|
295
322
|
return out
|
|
296
323
|
}
|
|
297
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Знаходить pg-leftover виклики `<obj>.connect(...)` / `<obj>.end(...)` без маркера
|
|
327
|
+
* `// allow-pg-leftover: <reason>` у файлах, де **в цьому ж файлі** є `import { sql|SQL } from 'bun'`.
|
|
328
|
+
*
|
|
329
|
+
* Скоп навмисно вузький: ці метод-імена занадто загальні (WebSocket, Stream, інші бібліотеки),
|
|
330
|
+
* тож сканер обмежений файлами, що вже використовують Bun SQL — там pg-залишок є явним
|
|
331
|
+
* багом міграції. У не-Bun-SQL файлах прапоратися не буде, навіть якщо проєкт у цілому
|
|
332
|
+
* мігрував.
|
|
333
|
+
* @param {string} content вихідний код
|
|
334
|
+
* @param {string} [virtualPath] шлях для вибору `lang`
|
|
335
|
+
* @returns {{ line: number, snippet: string, methodName: 'connect' | 'end' }[]} список порушень
|
|
336
|
+
*/
|
|
337
|
+
export function findBunSqlPgLeftoverCallInText(content, virtualPath = 'scan.ts') {
|
|
338
|
+
if (!textHasBunSqlImport(content)) return []
|
|
339
|
+
const parsed = parseProgramAndCommentsOrNull(content, virtualPath)
|
|
340
|
+
if (!parsed) return []
|
|
341
|
+
const { program, comments } = parsed
|
|
342
|
+
|
|
343
|
+
/** @type {{ line: number, snippet: string, methodName: 'connect' | 'end' }[]} */
|
|
344
|
+
const out = []
|
|
345
|
+
walkAstWithAncestors(program, [], node => {
|
|
346
|
+
const m = asPgLeftoverCall(node)
|
|
347
|
+
if (!m) return
|
|
348
|
+
if (hasMarkerCommentNear(node, comments, content, ALLOW_PG_LEFTOVER_MARKER_RE)) return
|
|
349
|
+
out.push({
|
|
350
|
+
line: offsetToLine(content, node.start),
|
|
351
|
+
snippet: normalizeSnippet(content.slice(node.start, node.end)),
|
|
352
|
+
methodName: m.name
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
return out
|
|
356
|
+
}
|
|
357
|
+
|
|
298
358
|
/**
|
|
299
359
|
* Знаходить динамічні SQL-списки у TaggedTemplateExpression / TemplateLiteral в контексті
|
|
300
360
|
* `IN (...)` або `VALUES (...)`, де серед expressions є виклик `.join(...)`.
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
-
"plugins": [
|
|
4
|
-
|
|
3
|
+
"plugins": [
|
|
4
|
+
"unicorn",
|
|
5
|
+
"oxc",
|
|
6
|
+
"import",
|
|
7
|
+
"jsdoc",
|
|
8
|
+
"promise",
|
|
9
|
+
"node",
|
|
10
|
+
"vue"
|
|
11
|
+
],
|
|
12
|
+
"jsPlugins": [
|
|
13
|
+
"@e18e/eslint-plugin"
|
|
14
|
+
],
|
|
5
15
|
"categories": {},
|
|
6
16
|
"rules": {
|
|
7
17
|
"e18e/prefer-includes": "error",
|
|
@@ -383,5 +393,8 @@
|
|
|
383
393
|
"builtin": true
|
|
384
394
|
},
|
|
385
395
|
"globals": {},
|
|
386
|
-
"ignorePatterns": [
|
|
396
|
+
"ignorePatterns": [
|
|
397
|
+
"**/schema.graphql",
|
|
398
|
+
"**/auto-imports.d.ts"
|
|
399
|
+
]
|
|
387
400
|
}
|