@7n/rules-lang-js 0.3.1 → 0.4.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.
- package/CHANGELOG.md +6 -0
- package/package.json +14 -4
- package/rules/bun/bunfig/bunfig.mdc +17 -0
- package/rules/bun/bunfig/bunfig.rego +29 -0
- package/rules/bun/bunfig/concern.json +9 -0
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
- package/rules/bun/docs/index.md +11 -0
- package/rules/bun/layout/concern.json +16 -0
- package/rules/bun/layout/docs/fix-layout.md +29 -0
- package/rules/bun/layout/docs/main.md +34 -0
- package/rules/bun/layout/fix-layout.mjs +63 -0
- package/rules/bun/layout/layout.mdc +60 -0
- package/rules/bun/layout/main.mjs +53 -0
- package/rules/bun/licensee/concern.json +7 -0
- package/rules/bun/licensee/docs/fix-licensee.md +27 -0
- package/rules/bun/licensee/docs/index.md +12 -0
- package/rules/bun/licensee/docs/main.md +30 -0
- package/rules/bun/licensee/fix-licensee.mjs +31 -0
- package/rules/bun/licensee/main.mjs +68 -0
- package/rules/bun/lint-surface/concern.json +3 -0
- package/rules/bun/lint-surface/lint-surface.mdc +13 -0
- package/rules/bun/main.json +1 -0
- package/rules/bun/main.mdc +11 -0
- package/rules/bun/package_json/concern.json +9 -0
- package/rules/bun/package_json/docs/fix-package_json.md +28 -0
- package/rules/bun/package_json/docs/index.md +9 -0
- package/rules/bun/package_json/fix-package_json.mjs +311 -0
- package/rules/bun/package_json/package_json.mdc +14 -0
- package/rules/bun/package_json/package_json.rego +64 -0
- package/rules/bun/package_json/template/package.json.deny.json +4 -0
- package/rules/js/check/check.mdc +26 -0
- package/rules/js/check/concern.json +19 -0
- package/rules/js/check/docs/eslint-config.md +56 -0
- package/rules/js/check/docs/fix-check.md +48 -0
- package/rules/js/check/docs/index.md +13 -0
- package/rules/js/check/docs/main.md +48 -0
- package/rules/js/check/eslint-config.mjs +262 -0
- package/rules/js/check/fix-check.mjs +82 -0
- package/rules/js/check/main.mjs +310 -0
- package/rules/js/dep-policy/concern.json +4 -0
- package/rules/js/dep-policy/dep-policy.mdc +36 -0
- package/rules/js/dep-policy/docs/main.md +35 -0
- package/rules/js/dep-policy/main.mjs +99 -0
- package/rules/js/docs/index.md +11 -0
- package/rules/js/eslint/concern.json +8 -0
- package/rules/js/eslint/docs/fix-eslint.md +50 -0
- package/rules/js/eslint/docs/fix-worker.md +31 -0
- package/rules/js/eslint/docs/index.md +11 -0
- package/rules/js/eslint/docs/main.md +35 -0
- package/rules/js/eslint/fix-eslint.mjs +160 -0
- package/rules/js/eslint/fix-worker.mjs +139 -0
- package/rules/js/eslint/main.mjs +115 -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/concern.json +11 -0
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
- package/rules/js/jscpd_config/docs/index.md +9 -0
- package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
- package/rules/js/jscpd_config/jscpd_config.rego +44 -0
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
- package/rules/js/jscpd_duplicates/concern.json +7 -0
- package/rules/js/jscpd_duplicates/docs/main.md +29 -0
- package/rules/js/jscpd_duplicates/main.mjs +69 -0
- package/rules/js/knip/concern.json +7 -0
- package/rules/js/knip/docs/main.md +32 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +68 -0
- package/rules/js/lint-findings/concern.json +3 -0
- package/rules/js/lint-findings/docs/main.md +40 -0
- package/rules/js/lint-findings/main.mjs +125 -0
- package/rules/js/main.json +1 -0
- package/rules/js/main.mdc +18 -0
- package/rules/js/package_json/concern.json +9 -0
- package/rules/js/package_json/docs/fix-package_json.md +25 -0
- package/rules/js/package_json/docs/index.md +9 -0
- package/rules/js/package_json/fix-package_json.mjs +3 -0
- package/rules/js/package_json/package_json.mdc +15 -0
- package/rules/js/package_json/package_json.rego +142 -0
- package/rules/js/package_json/template/package.json.snippet.json +6 -0
- package/rules/js/tooling/concern.json +3 -0
- package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
- package/rules/js/tooling/docs/main.md +53 -0
- package/rules/js/tooling/main.mjs +183 -0
- package/rules/js/utils_imports/concern.json +4 -0
- package/rules/js/utils_imports/docs/main.md +50 -0
- package/rules/js/utils_imports/main.mjs +185 -0
- package/rules/js/utils_imports/utils_imports.mdc +15 -0
- package/rules/js/vscode_extensions/concern.json +11 -0
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/js/vscode_extensions/docs/index.md +11 -0
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
- package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js/vscode_extensions/vscode_extensions.rego +12 -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/docs/index.md +11 -0
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
- package/rules/js-bun-db/lib/docs/index.md +11 -0
- package/rules/js-bun-db/main.json +1 -0
- package/rules/js-bun-db/main.mdc +8 -0
- package/rules/js-bun-db/package_json/concern.json +9 -0
- package/rules/js-bun-db/package_json/package_json.mdc +31 -0
- package/rules/js-bun-db/package_json/package_json.rego +15 -0
- package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
- 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/concern.json +4 -0
- package/rules/js-bun-db/safety/docs/main.md +34 -0
- package/rules/js-bun-db/safety/main.mjs +430 -0
- package/rules/js-bun-db/safety/safety.mdc +458 -0
- package/rules/js-bun-redis/docs/index.md +11 -0
- package/rules/js-bun-redis/imports/concern.json +4 -0
- package/rules/js-bun-redis/imports/docs/main.md +36 -0
- package/rules/js-bun-redis/imports/imports.mdc +47 -0
- package/rules/js-bun-redis/imports/main.mjs +88 -0
- package/rules/js-bun-redis/lib/docs/index.md +11 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
- package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
- package/rules/js-bun-redis/main.json +1 -0
- package/rules/js-bun-redis/main.mdc +8 -0
- package/rules/js-bun-redis/package_json/concern.json +9 -0
- package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
- package/rules/js-bun-redis/package_json/package_json.rego +15 -0
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
- package/rules/js-mssql/deps/concern.json +4 -0
- package/rules/js-mssql/deps/docs/main.md +33 -0
- package/rules/js-mssql/deps/main.mjs +297 -0
- package/rules/js-mssql/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
- package/rules/js-mssql/main.json +1 -0
- package/rules/js-mssql/main.mdc +144 -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-mssql/package_json/concern.json +9 -0
- package/rules/js-mssql/package_json/package_json.mdc +9 -0
- package/rules/js-mssql/package_json/package_json.rego +57 -0
- package/rules/js-run/configmap/concern.json +9 -0
- package/rules/js-run/configmap/configmap.mdc +37 -0
- package/rules/js-run/configmap/configmap.rego +21 -0
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/docs/index.md +11 -0
- package/rules/js-run/jsconfig/concern.json +9 -0
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
- package/rules/js-run/jsconfig/docs/index.md +9 -0
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
- package/rules/js-run/jsconfig/jsconfig.rego +59 -0
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/lib/bunyan-imports.mjs +98 -0
- package/rules/js-run/lib/check-env-scan.mjs +338 -0
- package/rules/js-run/lib/conn-file-rules.mjs +214 -0
- package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
- package/rules/js-run/lib/docs/check-env-scan.md +438 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
- package/rules/js-run/lib/docs/index.md +16 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
- package/rules/js-run/lib/docs/temporal-scan.md +29 -0
- package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
- package/rules/js-run/lib/temporal-scan.mjs +52 -0
- package/rules/js-run/main.json +1 -0
- package/rules/js-run/main.mdc +16 -0
- package/rules/js-run/package_json/concern.json +9 -0
- package/rules/js-run/package_json/package_json.mdc +44 -0
- package/rules/js-run/package_json/package_json.rego +37 -0
- package/rules/js-run/package_json/template/package.json.deny.json +22 -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/concern.json +12 -0
- package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
- package/rules/js-run/runtime/docs/main.md +35 -0
- package/rules/js-run/runtime/fix-runtime.mjs +46 -0
- package/rules/js-run/runtime/main.mjs +496 -0
- package/rules/js-run/runtime/runtime.mdc +184 -0
- package/rules/js-run/scope/concern.json +3 -0
- package/rules/js-run/scope/scope.mdc +11 -0
- package/rules/npm-module/docs/index.md +11 -0
- package/rules/npm-module/emit_types_config/concern.json +9 -0
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
- package/rules/npm-module/emit_types_config/docs/index.md +9 -0
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
- package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/header_doc_pointer/concern.json +5 -0
- package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
- package/rules/npm-module/main.json +1 -0
- package/rules/npm-module/main.mdc +36 -0
- package/rules/npm-module/npm_package_json/concern.json +9 -0
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
- package/rules/npm-module/npm_package_json/docs/index.md +9 -0
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
- package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/package_structure/concern.json +5 -0
- package/rules/npm-module/package_structure/docs/main.md +37 -0
- package/rules/npm-module/package_structure/main.mjs +448 -0
- package/rules/npm-module/package_structure/package_structure.mdc +63 -0
- package/rules/npm-module/root_package_json/concern.json +9 -0
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
- package/rules/npm-module/root_package_json/docs/index.md +9 -0
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
- package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
- package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/rule_meta/concern.json +8 -0
- package/rules/npm-module/rule_meta/docs/main.md +35 -0
- package/rules/npm-module/rule_meta/main.mjs +119 -0
- package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
- package/rules/npm-module/skill_meta/concern.json +5 -0
- package/rules/npm-module/skill_meta/docs/main.md +149 -0
- package/rules/npm-module/skill_meta/main.mjs +91 -0
- package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
- package/rules/tool-surface/docs/index.md +11 -0
- package/rules/tool-surface/main.json +6 -0
- package/rules/tool-surface/main.mdc +72 -0
- package/rules/vue/composition-api/composition-api.mdc +82 -0
- package/rules/vue/composition-api/concern.json +3 -0
- package/rules/vue/docs/index.md +11 -0
- package/rules/vue/lib/docs/index.md +11 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
- package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
- package/rules/vue/main.json +1 -0
- package/rules/vue/main.mdc +18 -0
- package/rules/vue/nheader-layout/concern.json +3 -0
- package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
- package/rules/vue/package_json/concern.json +9 -0
- package/rules/vue/package_json/package_json.mdc +30 -0
- package/rules/vue/package_json/package_json.rego +140 -0
- package/rules/vue/packages/concern.json +6 -0
- package/rules/vue/packages/docs/index.md +11 -0
- package/rules/vue/packages/docs/main.md +35 -0
- package/rules/vue/packages/main.mjs +575 -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,430 @@
|
|
|
1
|
+
/** @see ./docs/safety.md */
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { join, relative } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
7
|
+
import {
|
|
8
|
+
findBunSqlPerRequestConnectionInText,
|
|
9
|
+
findBunSqlPgLeftoverCallInText,
|
|
10
|
+
findBunSqlUnsafeUseWithoutAllowMarkerInText,
|
|
11
|
+
findBunSqlUnsafeWithInterpolatedTemplateInText,
|
|
12
|
+
findJsonStringifyBeforeJsonbInText,
|
|
13
|
+
findPgFormatLikeQueryWrapperInText,
|
|
14
|
+
findPgFormatShimDefinitionInText,
|
|
15
|
+
findPgLibImportInText,
|
|
16
|
+
findPgListenNotifyUsageInText,
|
|
17
|
+
findSqlArrayWithoutTypeArgInText,
|
|
18
|
+
findUnsafeBunSqlDynamicSqlListInText,
|
|
19
|
+
findUnsafeBunSqlInListMissingEmptyGuardInText,
|
|
20
|
+
isBunSqlScanSourceFile,
|
|
21
|
+
textHasBunSqlImport,
|
|
22
|
+
textHasPgLibImport
|
|
23
|
+
} from '../lib/bun-sql-scan.mjs'
|
|
24
|
+
import { findAllPackageJsonPaths } from '@7n/rules/scripts/utils/find-package-json-paths.mjs'
|
|
25
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
26
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
27
|
+
|
|
28
|
+
// Дешеві pre-filter regex'и для AST-сканера LISTEN/NOTIFY: уникаємо парсингу
|
|
29
|
+
// файлів, у яких ніяких сигналів немає. Винесено в модульний скоуп, щоб не
|
|
30
|
+
// перекомпілювати RegExp на кожному виклику `collectPgUsageForFile`.
|
|
31
|
+
const LISTEN_NOTIFY_KEYWORD_RE = /\b(LISTEN|UNLISTEN|NOTIFY)\b/iu
|
|
32
|
+
const NOTIFICATION_LITERAL_RE = /['"`]notification['"`]/u
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану Bun SQL патернів.
|
|
36
|
+
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
37
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
38
|
+
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
39
|
+
*/
|
|
40
|
+
async function findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths) {
|
|
41
|
+
/** @type {string[]} */
|
|
42
|
+
const paths = []
|
|
43
|
+
await walkDir(
|
|
44
|
+
repoRoot,
|
|
45
|
+
absPath => {
|
|
46
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
47
|
+
if (isBunSqlScanSourceFile(rel)) {
|
|
48
|
+
paths.push(absPath)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
ignorePaths
|
|
52
|
+
)
|
|
53
|
+
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
54
|
+
return paths
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Сканує JS/TS-джерела на небезпечні патерни Bun SQL і збирає метадані про
|
|
59
|
+
* використання `pg`/LISTEN-NOTIFY (для виключення dependency `pg`).
|
|
60
|
+
* @param {string[]} sourcePaths абсолютні шляхи джерел
|
|
61
|
+
* @param {string} repoRoot абсолютний шлях до кореня
|
|
62
|
+
* @param {{ pass: (m: string) => void, fail: (m: string) => void }} reporter колбеки pass і fail з перевірки
|
|
63
|
+
* @returns {Promise<{
|
|
64
|
+
* hasBunSqlImport: boolean,
|
|
65
|
+
* perRequest: number,
|
|
66
|
+
* unsafeCall: number,
|
|
67
|
+
* dynamicList: number,
|
|
68
|
+
* inListGuard: number,
|
|
69
|
+
* pgLeftover: number,
|
|
70
|
+
* pgFormatShim: number,
|
|
71
|
+
* queryWrapper: number,
|
|
72
|
+
* pgUsage: { rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }[]
|
|
73
|
+
* }>}
|
|
74
|
+
* `hasBunSqlImport` — чи є хоч один `import { sql|SQL } from 'bun'`;
|
|
75
|
+
* `pgUsage` — список файлів, що або імпортують `'pg'`, або містять LISTEN/NOTIFY-патерн
|
|
76
|
+
* (інші — пропущено, щоб не тримати в пам'яті метадані про всі файли).
|
|
77
|
+
*/
|
|
78
|
+
async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
|
|
79
|
+
const { fail } = reporter
|
|
80
|
+
const counts = {
|
|
81
|
+
perRequest: 0,
|
|
82
|
+
unsafeCall: 0,
|
|
83
|
+
unsafeTemplateInterp: 0,
|
|
84
|
+
dynamicList: 0,
|
|
85
|
+
inListGuard: 0,
|
|
86
|
+
pgLeftover: 0,
|
|
87
|
+
pgFormatShim: 0,
|
|
88
|
+
queryWrapper: 0,
|
|
89
|
+
jsonStringifyJsonb: 0,
|
|
90
|
+
sqlArrayNoType: 0
|
|
91
|
+
}
|
|
92
|
+
let hasBunSqlImport = false
|
|
93
|
+
/** @type {{ rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }[]} */
|
|
94
|
+
const pgUsage = []
|
|
95
|
+
|
|
96
|
+
for (const absPath of sourcePaths) {
|
|
97
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
98
|
+
const content = await readFile(absPath, 'utf8')
|
|
99
|
+
if (!hasBunSqlImport && textHasBunSqlImport(content)) {
|
|
100
|
+
hasBunSqlImport = true
|
|
101
|
+
}
|
|
102
|
+
scanFileForBunSqlPatterns(content, rel, fail, counts)
|
|
103
|
+
collectPgUsageForFile(content, rel, pgUsage)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { hasBunSqlImport, pgUsage, ...counts }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Якщо у файлі є імпорт `'pg'` АБО LISTEN/NOTIFY-патерн — додає запис у `pgUsage`.
|
|
111
|
+
* Файли без жодного сигналу не зберігаються, щоб уникнути зайвої пам'яті.
|
|
112
|
+
* @param {string} content вміст файлу
|
|
113
|
+
* @param {string} rel posix-шлях відносно кореня
|
|
114
|
+
* @param {{ rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }[]} pgUsage акумулятор
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
function collectPgUsageForFile(content, rel, pgUsage) {
|
|
118
|
+
// Дешевий pre-filter за текстом: AST-парсинг тільки коли файл містить
|
|
119
|
+
// або імпорт `'pg'`, або хоча б одне зі слів LISTEN / NOTIFY / UNLISTEN /
|
|
120
|
+
// 'notification' — інакше LISTEN/NOTIFY у ньому точно немає.
|
|
121
|
+
const mayHaveListenNotify = LISTEN_NOTIFY_KEYWORD_RE.test(content) || NOTIFICATION_LITERAL_RE.test(content)
|
|
122
|
+
if (!textHasPgLibImport(content) && !mayHaveListenNotify) return
|
|
123
|
+
const imports = findPgLibImportInText(content, rel)
|
|
124
|
+
const listenNotify = findPgListenNotifyUsageInText(content, rel)
|
|
125
|
+
if (imports.length === 0 && listenNotify.length === 0) return
|
|
126
|
+
pgUsage.push({ rel, imports, listenNotify })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Сканує один файл усіма AST-сканерами bun-sql і реєструє знайдені порушення.
|
|
131
|
+
* @param {string} content вміст файлу
|
|
132
|
+
* @param {string} rel posix-шлях відносно `repoRoot`
|
|
133
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
134
|
+
* @param {{ perRequest: number, unsafeCall: number, unsafeTemplateInterp: number, dynamicList: number, inListGuard: number, pgLeftover: number, pgFormatShim: number, queryWrapper: number, jsonStringifyJsonb: number, sqlArrayNoType: number }} counts акумулятори
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*/
|
|
137
|
+
function scanFileForBunSqlPatterns(content, rel, fail, counts) {
|
|
138
|
+
for (const v of findBunSqlPerRequestConnectionInText(content, rel)) {
|
|
139
|
+
counts.perRequest++
|
|
140
|
+
fail(
|
|
141
|
+
`js-bun-db: ${rel}:${v.line} — не створюй new SQL(...) всередині функцій; ` +
|
|
142
|
+
`тримай singleton на рівні модуля (js-bun-db.mdc): ${v.snippet}`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
for (const v of findBunSqlUnsafeUseWithoutAllowMarkerInText(content, rel)) {
|
|
146
|
+
counts.unsafeCall++
|
|
147
|
+
fail(
|
|
148
|
+
`js-bun-db: ${rel}:${v.line} — sql.unsafe(...) заборонено за замовчуванням; ` +
|
|
149
|
+
`допустимо лише для підстановки назви таблиці/колонки чи dynamic SQL/DDL з code-controlled значенням, ` +
|
|
150
|
+
`інакше переробити на tagged template sql\`...\${value}...\`. ` +
|
|
151
|
+
`Якщо випадок легітимний — додай маркер "// allow-unsafe: <причина>" на тому ж рядку або рядком вище ` +
|
|
152
|
+
`(js-bun-db.mdc): ${v.snippet}`
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
for (const v of findBunSqlUnsafeWithInterpolatedTemplateInText(content, rel)) {
|
|
156
|
+
counts.unsafeTemplateInterp++
|
|
157
|
+
fail(
|
|
158
|
+
`js-bun-db: ${rel}:${v.line} — sql.unsafe(\`...\${x}...\`) з template-літералом і \${...}-інтерполяцією ` +
|
|
159
|
+
`заборонено навіть з allow-unsafe маркером: шаблонна підстановка identifier'у не екранує (reserved words, ` +
|
|
160
|
+
`спецсимволи), а значення не біндяться. Збери text через @scaleleap/pg-format format('%I', name) для ` +
|
|
161
|
+
`identifiers або позиційні $N для values, потім sql.unsafe(text, [params]). Деталі — секція ` +
|
|
162
|
+
`«Динамічна SQL-структура» в js-bun-db.mdc: ${v.snippet}`
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
for (const v of findBunSqlPgLeftoverCallInText(content, rel)) {
|
|
166
|
+
counts.pgLeftover++
|
|
167
|
+
fail(
|
|
168
|
+
`js-bun-db: ${rel}:${v.line} — pg-leftover виклик .${v.methodName}(...): Bun SQL пулом керує сам, ` +
|
|
169
|
+
`видали зайвий .connect()/.end() або, якщо випадок легітимний (graceful shutdown тощо), ` +
|
|
170
|
+
`додай маркер "// allow-pg-leftover: <причина>" на тому ж рядку або рядком вище ` +
|
|
171
|
+
`(js-bun-db.mdc): ${v.snippet}`
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
for (const v of findUnsafeBunSqlDynamicSqlListInText(content, rel)) {
|
|
175
|
+
counts.dynamicList++
|
|
176
|
+
fail(
|
|
177
|
+
`js-bun-db: ${rel}:${v.line} — заборонено підставляти у SQL динамічні списки через .join(',') ` +
|
|
178
|
+
`у IN (...) / VALUES (...); використовуй sql([...]) (js-bun-db.mdc): ${v.snippet}`
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
for (const v of findUnsafeBunSqlInListMissingEmptyGuardInText(content, rel)) {
|
|
182
|
+
counts.inListGuard++
|
|
183
|
+
fail(messageForBunSqlInListGuard(rel, v))
|
|
184
|
+
}
|
|
185
|
+
for (const v of findPgFormatShimDefinitionInText(content, rel)) {
|
|
186
|
+
counts.pgFormatShim++
|
|
187
|
+
if (v.kind === 'format_function') {
|
|
188
|
+
fail(
|
|
189
|
+
`js-bun-db: ${rel}:${v.line} — функція ${JSON.stringify(v.name)} виглядає як pg-format-сумісний шим ` +
|
|
190
|
+
`(тіло містить %L / %I / %s). Видали шим і переведи всі call-site на tagged template ` +
|
|
191
|
+
`sql\`...\${value}...\` (js-bun-db.mdc): ${v.snippet}`
|
|
192
|
+
)
|
|
193
|
+
} else {
|
|
194
|
+
fail(
|
|
195
|
+
`js-bun-db: ${rel}:${v.line} — ${JSON.stringify(v.name)} — це pg-format-специфічний escape-хелпер; ` +
|
|
196
|
+
`з Bun SQL він не потрібен (параметризація через tagged template), видали і перепиши call-site ` +
|
|
197
|
+
`(js-bun-db.mdc): ${v.snippet}`
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const v of findPgFormatLikeQueryWrapperInText(content, rel)) {
|
|
202
|
+
counts.queryWrapper++
|
|
203
|
+
fail(
|
|
204
|
+
`js-bun-db: ${rel}:${v.line} — query(text, params)-обгортка над <obj>.unsafe(...) — це прихований ` +
|
|
205
|
+
`pg-сумісний шим. Видали обгортку (pgRead/pgWrite/db.query) і переведи всі call-site на tagged template ` +
|
|
206
|
+
`sql\`...\${value}...\` (js-bun-db.mdc): ${v.snippet}`
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
for (const v of findJsonStringifyBeforeJsonbInText(content, rel)) {
|
|
210
|
+
counts.jsonStringifyJsonb++
|
|
211
|
+
fail(
|
|
212
|
+
`js-bun-db: ${rel}:${v.line} — JSON.stringify(...) перед ::jsonb зайвий: Bun SQL серіалізує ` +
|
|
213
|
+
`об'єкти/масиви у JSON автоматично, явний stringify призводить до подвійної серіалізації ` +
|
|
214
|
+
`(js-bun-db.mdc query-safety): ${v.snippet}`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
for (const v of findSqlArrayWithoutTypeArgInText(content, rel)) {
|
|
218
|
+
counts.sqlArrayNoType++
|
|
219
|
+
fail(
|
|
220
|
+
`js-bun-db: ${rel}:${v.line} — sql.array(arr) без другого аргументу типу — ` +
|
|
221
|
+
`вкажи явний pg-тип: sql.array(arr, 'int8') / sql.array(arr, 'uuid') тощо ` +
|
|
222
|
+
`(js-bun-db.mdc sql-array): ${v.snippet}`
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Перевіряє виключення `pg` для LISTEN/NOTIFY: по кожному `package.json` з
|
|
229
|
+
* `dependencies.pg` — чи є у проекті хоч одне використання LISTEN/NOTIFY-патерну;
|
|
230
|
+
* додатково — кожен файл з `import 'pg'` повинен сам містити LISTEN/NOTIFY (інакше
|
|
231
|
+
* звичайні SELECT/INSERT/UPDATE через `pg` ховаються за легітимним dependency).
|
|
232
|
+
* @param {string[]} pkgJsonPaths абсолютні шляхи до всіх package.json
|
|
233
|
+
* @param {string} repoRoot абсолютний шлях до кореня
|
|
234
|
+
* @param {{ rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }[]} pgUsage метадані з scanSourcesForBunSqlPatterns
|
|
235
|
+
* @param {{ fail: (m: string) => void }} reporter колбек fail для повідомлень
|
|
236
|
+
* @returns {Promise<{ pgDepFails: number, pgImportFails: number, pgDepsFound: number, hasAnyListenNotify: boolean, listenNotifyEvidence: string | null }>}
|
|
237
|
+
* counters і метадані для підсумкового `pass`-повідомлення (де саме знайдено перший LISTEN/NOTIFY).
|
|
238
|
+
*/
|
|
239
|
+
async function checkPgDependencyAndUsage(pkgJsonPaths, repoRoot, pgUsage, reporter) {
|
|
240
|
+
const { fail } = reporter
|
|
241
|
+
let pgDepFails = 0
|
|
242
|
+
let pgImportFails = 0
|
|
243
|
+
let pgDepsFound = 0
|
|
244
|
+
|
|
245
|
+
const firstWithListenNotify = pgUsage.find(u => u.listenNotify.length > 0)
|
|
246
|
+
const hasAnyListenNotify = !!firstWithListenNotify
|
|
247
|
+
const listenNotifyEvidence = firstWithListenNotify
|
|
248
|
+
? `${firstWithListenNotify.rel}:${firstWithListenNotify.listenNotify[0].line}`
|
|
249
|
+
: null
|
|
250
|
+
|
|
251
|
+
for (const absPkgPath of pkgJsonPaths) {
|
|
252
|
+
const relPkg = relative(repoRoot, absPkgPath).split('\\').join('/')
|
|
253
|
+
let pkg
|
|
254
|
+
try {
|
|
255
|
+
pkg = JSON.parse(await readFile(absPkgPath, 'utf8'))
|
|
256
|
+
} catch {
|
|
257
|
+
// невалідний JSON у package.json — це проблема інших правил, тут пропускаємо
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
if (!pkg || typeof pkg !== 'object') continue
|
|
261
|
+
const deps = pkg.dependencies
|
|
262
|
+
if (!deps || typeof deps !== 'object' || !Object.hasOwn(deps, 'pg')) continue
|
|
263
|
+
pgDepsFound++
|
|
264
|
+
if (!hasAnyListenNotify) {
|
|
265
|
+
pgDepFails++
|
|
266
|
+
fail(
|
|
267
|
+
`js-bun-db: ${relPkg}: dependencies.pg заборонено — у проекті не знайдено LISTEN / NOTIFY / UNLISTEN ` +
|
|
268
|
+
`(або listener'а .on('notification', ...)). Bun SQL покриває звичайні запити; ` +
|
|
269
|
+
`\`pg\` дозволений лише як виняток для LISTEN/NOTIFY (js-bun-db.mdc, ` +
|
|
270
|
+
`секція «pg для LISTEN/NOTIFY»)`
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const f of pgUsage) {
|
|
276
|
+
if (f.imports.length === 0) continue
|
|
277
|
+
if (f.listenNotify.length > 0) continue
|
|
278
|
+
for (const imp of f.imports) {
|
|
279
|
+
pgImportFails++
|
|
280
|
+
fail(
|
|
281
|
+
`js-bun-db: ${f.rel}:${imp.line} — import 'pg' дозволено лише у файлах з LISTEN / NOTIFY / UNLISTEN ` +
|
|
282
|
+
`або .on('notification', ...). Перенеси звичайні запити на Bun SQL ` +
|
|
283
|
+
`(import { sql } from 'bun'), а LISTEN/NOTIFY-логіку лиши в окремому модулі ` +
|
|
284
|
+
`(js-bun-db.mdc): ${imp.snippet}`
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { pgDepFails, pgImportFails, pgDepsFound, hasAnyListenNotify, listenNotifyEvidence }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Будує повідомлення `fail` для порушення `findUnsafeBunSqlInListMissingEmptyGuardInText`
|
|
294
|
+
* залежно від `reason` (різні діагностики однакового сімейства).
|
|
295
|
+
* @param {string} rel posix-шлях відносно кореня репо
|
|
296
|
+
* @param {{ line: number, snippet: string, name?: string, reason: string }} v порушення
|
|
297
|
+
* @returns {string} готове повідомлення для `fail`
|
|
298
|
+
*/
|
|
299
|
+
function messageForBunSqlInListGuard(rel, v) {
|
|
300
|
+
if (v.reason === 'missing_guard') {
|
|
301
|
+
return (
|
|
302
|
+
`js-bun-db: ${rel}:${v.line} — перед IN-списком ${JSON.stringify(v.name)} потрібна перевірка на пустоту ` +
|
|
303
|
+
`з throw (наприклад if (!${v.name}.length) throw ...), інакше можливі некоректні запити (js-bun-db.mdc): ${v.snippet}`
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
if (v.reason === 'sql_helper_not_var') {
|
|
307
|
+
return (
|
|
308
|
+
`js-bun-db: ${rel}:${v.line} — IN-список у \${sql(...)} має підставлятись зі змінної (Identifier) ` +
|
|
309
|
+
`після валідації на пустоту + throw (js-bun-db.mdc): ${v.snippet}`
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
return (
|
|
313
|
+
`js-bun-db: ${rel}:${v.line} — значення для IN (...) у template literal треба винести в окрему змінну ` +
|
|
314
|
+
`і перевірити на пустоту (throw), не підставляти вираз напряму (js-bun-db.mdc): ${v.snippet}`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Перевіряє відповідність проєкту правилу js-bun-db.mdc
|
|
320
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
|
|
321
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} перелік порушень
|
|
322
|
+
*/
|
|
323
|
+
export async function lint(ctx) {
|
|
324
|
+
const cwd = ctx.cwd
|
|
325
|
+
const reporter = createViolationReporter(ctx)
|
|
326
|
+
const { pass } = reporter
|
|
327
|
+
|
|
328
|
+
const repoRoot = cwd
|
|
329
|
+
const rootPkg = join(repoRoot, 'package.json')
|
|
330
|
+
if (!existsSync(rootPkg)) {
|
|
331
|
+
pass('js-bun-db: package.json у корені відсутній — перевірку пропущено')
|
|
332
|
+
return reporter.result()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const ignorePaths = await loadCursorIgnorePaths(repoRoot)
|
|
336
|
+
const pkgJsonPaths = await findAllPackageJsonPaths(repoRoot, ignorePaths)
|
|
337
|
+
if (pkgJsonPaths.length === 0) {
|
|
338
|
+
pass('js-bun-db: package.json не знайдено — перевірку пропущено')
|
|
339
|
+
return reporter.result()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Заборону `pg-format` / `mysql2` у `dependencies` тримає Rego-поліс
|
|
343
|
+
// `npm/policy/js_bun_db/package_json/`. `pg` оброблено тут — як виняток для
|
|
344
|
+
// LISTEN/NOTIFY (Rego не бачить JS-коду, тож не може зважити сигнал).
|
|
345
|
+
|
|
346
|
+
const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths)
|
|
347
|
+
if (sourcePaths.length === 0) {
|
|
348
|
+
pass('js-bun-db: немає JS/TS файлів для скану патернів Bun SQL')
|
|
349
|
+
return reporter.result()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const {
|
|
353
|
+
hasBunSqlImport,
|
|
354
|
+
pgUsage,
|
|
355
|
+
perRequest,
|
|
356
|
+
unsafeCall,
|
|
357
|
+
unsafeTemplateInterp,
|
|
358
|
+
dynamicList,
|
|
359
|
+
inListGuard,
|
|
360
|
+
pgLeftover,
|
|
361
|
+
pgFormatShim,
|
|
362
|
+
queryWrapper,
|
|
363
|
+
jsonStringifyJsonb,
|
|
364
|
+
sqlArrayNoType
|
|
365
|
+
} = await scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter)
|
|
366
|
+
|
|
367
|
+
const { pgDepFails, pgImportFails, pgDepsFound, listenNotifyEvidence } = await checkPgDependencyAndUsage(
|
|
368
|
+
pkgJsonPaths,
|
|
369
|
+
repoRoot,
|
|
370
|
+
pgUsage,
|
|
371
|
+
reporter
|
|
372
|
+
)
|
|
373
|
+
if (pgDepFails === 0) {
|
|
374
|
+
if (pgDepsFound === 0) {
|
|
375
|
+
pass('js-bun-db: dependencies.pg відсутнє у жодному package.json')
|
|
376
|
+
} else {
|
|
377
|
+
pass(
|
|
378
|
+
`js-bun-db: dependencies.pg виправдано LISTEN/NOTIFY у коді (виключення з js-bun-db.mdc; ` +
|
|
379
|
+
`доказ: ${listenNotifyEvidence})`
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (pgImportFails === 0) {
|
|
384
|
+
pass("js-bun-db: усі `import 'pg'` або відсутні, або у файлах з LISTEN/NOTIFY")
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!hasBunSqlImport) {
|
|
388
|
+
pass("js-bun-db: Bun SQL не використовується в коді (немає import { sql|SQL } from 'bun')")
|
|
389
|
+
return reporter.result()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (perRequest === 0) {
|
|
393
|
+
pass('js-bun-db: немає створення new SQL(...) всередині функцій (singleton на рівні модуля)')
|
|
394
|
+
}
|
|
395
|
+
if (unsafeCall === 0) {
|
|
396
|
+
pass('js-bun-db: усі sql.unsafe(...) або відсутні, або супроводжуються маркером "// allow-unsafe: <причина>"')
|
|
397
|
+
}
|
|
398
|
+
if (unsafeTemplateInterp === 0) {
|
|
399
|
+
pass(
|
|
400
|
+
'js-bun-db: немає sql.unsafe(template literal з інтерполяцією) ' +
|
|
401
|
+
'(identifiers через @scaleleap/pg-format %I, values — позиційні $N)'
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
if (pgLeftover === 0) {
|
|
405
|
+
pass(
|
|
406
|
+
'js-bun-db: немає pg-leftover викликів .connect()/.end() у файлах з Bun SQL ' +
|
|
407
|
+
'(або всі вони мають маркер "// allow-pg-leftover: <причина>")'
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
if (dynamicList === 0) {
|
|
411
|
+
pass("js-bun-db: немає небезпечних динамічних SQL-списків через .join(',') у IN/VALUES")
|
|
412
|
+
}
|
|
413
|
+
if (inListGuard === 0) {
|
|
414
|
+
pass('js-bun-db: усі IN-списки винесені у змінні та мають перевірку на пустоту з throw')
|
|
415
|
+
}
|
|
416
|
+
if (pgFormatShim === 0) {
|
|
417
|
+
pass('js-bun-db: немає pg-format-сумісних шимів (format/quoteLiteral/quoteIdent/...) у файлах з Bun SQL')
|
|
418
|
+
}
|
|
419
|
+
if (queryWrapper === 0) {
|
|
420
|
+
pass('js-bun-db: немає query(text, params)-обгорток над unsafe(...) у файлах з Bun SQL')
|
|
421
|
+
}
|
|
422
|
+
if (jsonStringifyJsonb === 0) {
|
|
423
|
+
pass('js-bun-db: немає JSON.stringify(...) перед ::jsonb — Bun SQL серіалізує автоматично')
|
|
424
|
+
}
|
|
425
|
+
if (sqlArrayNoType === 0) {
|
|
426
|
+
pass('js-bun-db: усі sql.array() мають явний аргумент типу')
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return reporter.result()
|
|
430
|
+
}
|