@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,380 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: mssql-pool-scan.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js-mssql/lib/mssql-pool-scan.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 0e040e79
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Модуль `mssql-pool-scan.mjs` — це набір AST-сканерів, що шукають **небезпечні патерни використання драйвера `mssql`** (Microsoft SQL Server для Node.js) у вихідних файлах JavaScript / TypeScript. Сканери призначені для статичного аналізу й використовуються з правил `js-mssql` (див. `js-mssql.mdc`).
|
|
10
|
+
|
|
11
|
+
Модуль виявляє п'ять класів проблем:
|
|
12
|
+
|
|
13
|
+
1. **Створення `new sql.ConnectionPool(...)` / `new mssql.ConnectionPool(...)` всередині функції.** Це антипатерн: пул має бути singleton на рівні модуля, а не створюватися на кожен запит/виклик.
|
|
14
|
+
2. **Небезпечний виклик `query(\`...\`)`** — звичайний `CallExpression` з `TemplateLiteral` як першим аргументом (не tagged template). Це може призвести до **SQL injection**, бо інтерполяція відбувається на рівні JS і в SQL потрапляє вже склеєний рядок.
|
|
15
|
+
3. **Shared `Request`** — `export const request = pool.request()` (або `const request = pool.request()`), який не можна повторно використовувати між запитами в драйвері `mssql`.
|
|
16
|
+
4. **Динамічні SQL-списки через `.join(...)`** у `TemplateLiteral` / `TaggedTemplateExpression` у контексті `IN (...)` або `VALUES (...)`. Навіть у tagged template це небезпечно, бо в запит підставляється готовий шматок SQL.
|
|
17
|
+
5. **`IN (${...})` без числового парсера й/або без guard-перевірки на порожній список.** Навіть у безпечному tagged template значення треба явно приводити до Number/BigInt і відкидати NaN, а перед запитом — перевіряти, що список не порожній (`if (!ids.length) throw ...`).
|
|
18
|
+
|
|
19
|
+
Парсинг виконується через **`oxc-parser`** (`parseSync`). Якщо файл не парситься або містить синтаксичні помилки — кожна функція повертає **порожній масив** (треба спочатку полагодити синтаксис і перезапустити сканування).
|
|
20
|
+
|
|
21
|
+
Файл живе всередині `npm/rules/js-mssql/lib/` й експортує **6 публічних функцій** (5 сканерів + 1 фільтр розширень файлів). Допоміжні функції (перевірки AST-вузлів, трасування Identifier-ів) — приватні.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Експорти / API
|
|
26
|
+
|
|
27
|
+
| Експорт | Тип | Призначення |
|
|
28
|
+
| --------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------- |
|
|
29
|
+
| `findMssqlPerRequestConnectionInText(content, virtualPath?)` | function | Знайти `new sql.ConnectionPool(...)` / `new mssql.ConnectionPool(...)` всередині функцій. |
|
|
30
|
+
| `findUnsafeMssqlQueryTemplateCallInText(content, virtualPath?)` | function | Знайти `obj.query(\`...\`)`— небезпечну інтерполяцію в`query(...)`. |
|
|
31
|
+
| `findSharedMssqlRequestInText(content, virtualPath?)` | function | Знайти shared `Request`: `const request = something.request()`. |
|
|
32
|
+
| `findUnsafeMssqlDynamicSqlListInText(content, virtualPath?)` | function | Знайти `IN (...)` / `VALUES (...)` зі склеєним `.join(',')` у `${...}`. |
|
|
33
|
+
| `findUnsafeMssqlInListUnparsedInText(content, virtualPath?)` | function | Знайти `IN (${expr})`, де `expr` не пройшов числовий парсер. |
|
|
34
|
+
| `findUnsafeMssqlInListMissingEmptyGuardInText(content, virtualPath?)` | function | Знайти `IN (${...})` без guard `if (empty) throw` (або без винесення у змінну). |
|
|
35
|
+
| `isMssqlScanSourceFile(relativePathPosix)` | function | Фільтр файлів для сканування за розширенням. |
|
|
36
|
+
|
|
37
|
+
### Загальна форма результату сканерів
|
|
38
|
+
|
|
39
|
+
Усі сканери, окрім останнього missing-guard-сканера, повертають масив об'єктів виду:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
{
|
|
43
|
+
line: number, // 1-based номер рядка в content
|
|
44
|
+
snippet: string // нормалізований фрагмент вихідного коду (через normalizeSnippet)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Сканер `findUnsafeMssqlInListMissingEmptyGuardInText` додатково повертає поля `reason: 'not_var' | 'missing_guard'` та опційне `name: string` (імʼя Identifier-у, для якого не знайдено guard).
|
|
49
|
+
|
|
50
|
+
`isMssqlScanSourceFile` повертає `boolean`.
|
|
51
|
+
|
|
52
|
+
### Спільні параметри сканерів
|
|
53
|
+
|
|
54
|
+
- `content: string` — повний вихідний код файлу.
|
|
55
|
+
- `virtualPath: string` — необовʼязковий «віртуальний» шлях файлу (наприклад `pkg/src/db.ts`), потрібний `oxc-parser`, щоб обрати мову (`lang`) через `langFromPath`. За замовчуванням — `'scan.ts'`.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Функції
|
|
60
|
+
|
|
61
|
+
### Експортовані функції
|
|
62
|
+
|
|
63
|
+
#### `findMssqlPerRequestConnectionInText(content, virtualPath = 'scan.ts')`
|
|
64
|
+
|
|
65
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
|
|
66
|
+
- **Параметри:**
|
|
67
|
+
- `content` — вихідний код для сканування.
|
|
68
|
+
- `virtualPath` — шлях для вибору мови парсера (`lang`).
|
|
69
|
+
- **Повертає:** масив порушень `{ line, snippet }`. Порожній масив, якщо файл не парситься або немає порушень.
|
|
70
|
+
- **Що шукає:** `NewExpression` виду `new sql.ConnectionPool(...)` або `new mssql.ConnectionPool(...)` (через `isNewConnectionPool`), і лише ті, що **знаходяться всередині будь-якої функції** (перевірка через `ancestors.some(isFunctionNode)`).
|
|
71
|
+
- **Side effects:** немає (чиста функція). Не кидає винятків — парсинг обгорнуто в `try/catch`.
|
|
72
|
+
|
|
73
|
+
#### `findUnsafeMssqlQueryTemplateCallInText(content, virtualPath = 'scan.ts')`
|
|
74
|
+
|
|
75
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
|
|
76
|
+
- **Параметри:**
|
|
77
|
+
- `content` — вихідний код.
|
|
78
|
+
- `virtualPath` — шлях файлу для вибору `lang`.
|
|
79
|
+
- **Повертає:** масив `{ line, snippet }` для всіх `CallExpression`, що відповідають `<obj>.query(\`...\`)`(метод`.query`без квадратних дужок, перший аргумент —`TemplateLiteral`).
|
|
80
|
+
- **Side effects:** немає.
|
|
81
|
+
|
|
82
|
+
#### `findSharedMssqlRequestInText(content, virtualPath = 'scan.ts')`
|
|
83
|
+
|
|
84
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
|
|
85
|
+
- **Параметри:** як вище.
|
|
86
|
+
- **Повертає:** масив `{ line, snippet }`. Включає `VariableDeclarator`, у яких:
|
|
87
|
+
- `id` — `Identifier` з імʼям рівно `request`;
|
|
88
|
+
- `init` — `CallExpression` виду `<obj>.request()` (через `isRequestFactoryCall`).
|
|
89
|
+
- **Side effects:** немає.
|
|
90
|
+
|
|
91
|
+
#### `findUnsafeMssqlDynamicSqlListInText(content, virtualPath = 'scan.ts')`
|
|
92
|
+
|
|
93
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
|
|
94
|
+
- **Параметри:** як вище.
|
|
95
|
+
- **Повертає:** масив `{ line, snippet }`. Виявляє `TemplateLiteral` або `TaggedTemplateExpression` (через `.quasi`), які одночасно:
|
|
96
|
+
- Знаходяться в SQL-контексті списку `IN (...)` / `VALUES (...)` — через `isSqlListContextTemplate` (із `ast-scan-utils`).
|
|
97
|
+
- Містять у `template.expressions` хоча б одну `CallExpression` `.join(...)` — через `isJoinCall`.
|
|
98
|
+
- `line`/`snippet` беруться по координатам `template.start` / `template.end`.
|
|
99
|
+
- **Side effects:** немає.
|
|
100
|
+
|
|
101
|
+
#### `findUnsafeMssqlInListUnparsedInText(content, virtualPath = 'scan.ts')`
|
|
102
|
+
|
|
103
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
|
|
104
|
+
- **Параметри:** як вище.
|
|
105
|
+
- **Повертає:** масив `{ line, snippet }`. Виявляє `TemplateLiteral`, у яких:
|
|
106
|
+
- Quasi прямо перед `expressions[i]` закінчується на `IN (` (regex `IN_PLACEHOLDER_END_RE`, `/\bin\s*\(\s*$/iu`).
|
|
107
|
+
- Сам вираз `${...}` **не** є «безпечно числовим» за критеріями `isInListExpressionParsed` (літеральний масив чисел / піддерево з `parseInt|parseFloat|Number|BigInt|+x` / Identifier з безпечним `init`).
|
|
108
|
+
- Вираз — не `.join(...)` (це покривається окремим сканером `findUnsafeMssqlDynamicSqlListInText`).
|
|
109
|
+
- Перед скануванням збирає всі `VariableDeclarator`-и в програмі через `collectVariableDeclarators` — щоб трасувати Identifier до його init у тому ж файлі.
|
|
110
|
+
- `line` рахується по `expr.start` (або, якщо не визначено, по `node.start`); `snippet` — від `node.start` до `node.end`.
|
|
111
|
+
- **Side effects:** немає.
|
|
112
|
+
|
|
113
|
+
#### `findUnsafeMssqlInListMissingEmptyGuardInText(content, virtualPath = 'scan.ts')`
|
|
114
|
+
|
|
115
|
+
- **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string, reason: 'not_var' | 'missing_guard', name?: string }[]`
|
|
116
|
+
- **Параметри:** як вище.
|
|
117
|
+
- **Повертає:** масив порушень. На кожен `${expr}` у позиції `IN (...)`:
|
|
118
|
+
- Якщо `expr` — **не** `Identifier`, порушення з `reason: 'not_var'` (значення мали бути винесені у змінну, щоб мати точку для guard).
|
|
119
|
+
- Якщо `expr` — `Identifier` `name`, але в enclosing-блоці перед поточним statement немає guard `if (empty(name)) throw`, — порушення з `reason: 'missing_guard'` і `name: <identifier>`.
|
|
120
|
+
- Якщо guard є, порушення не додається.
|
|
121
|
+
- `line`/`snippet` беруться по `node.start` / `node.end` всього `TemplateLiteral`.
|
|
122
|
+
- **Side effects:** немає.
|
|
123
|
+
|
|
124
|
+
#### `isMssqlScanSourceFile(relativePathPosix)`
|
|
125
|
+
|
|
126
|
+
- **Сигнатура:** `(relativePathPosix: string) => boolean`
|
|
127
|
+
- **Параметри:** `relativePathPosix` — відносний шлях файлу у posix-форматі.
|
|
128
|
+
- **Повертає:** `true`, якщо файл має розширення `.js | .mjs | .cjs | .jsx | .ts | .mts | .cts | .tsx` (regex `SOURCE_FILE_RE` = `/\.([cm]?[jt]sx?)$/`) **і** не закінчується на `.d.ts` (декларації типів виключені).
|
|
129
|
+
- **Side effects:** немає.
|
|
130
|
+
|
|
131
|
+
### Приватні допоміжні функції
|
|
132
|
+
|
|
133
|
+
#### Локалізація `IN (...)` і числові гарантії
|
|
134
|
+
|
|
135
|
+
- `isZeroLiteral(node)` — `node` є літералом `0` (`NumericLiteral`/`Literal` зі значенням `0`).
|
|
136
|
+
- `isLengthMemberOf(node, name)` — `node` — некомпʼютоване `MemberExpression` `name.length`.
|
|
137
|
+
- `isEmptyListBinaryTest(test, name)` — `BinaryExpression` з оператором з `EMPTY_LIST_BINARY_OPERATORS` = `{ '===', '==', '<=', '<' }`, що порівнює `name.length` із літералом `0`. Для `===` і `==` (`EMPTY_LIST_REVERSED_OPERATORS`) дозволяється зворотний порядок (`0 === name.length`).
|
|
138
|
+
- `isEmptyListTest(test, name)` — тест if-умови виду `!name.length` або `name.length {===,==,<=} 0`, або `name.length < 1`. **Зауваження:** `<` із порівнянням з `0` через `isEmptyListBinaryTest` дає вираз `name.length < 0` (завжди false), що рідко зустрічається на практиці; типовий `length < 1` теж покривається через оператор `<` з правою частиною `1` — але `isZeroLiteral` приймає лише `0`, тож насправді `< 1` **не** розпізнається. (Це поведінка реалізації.)
|
|
139
|
+
- `consequentHasThrow(consequent)` — у consequent if-у (як один `ThrowStatement` або як `BlockStatement.body`) є `ThrowStatement`.
|
|
140
|
+
- `hasEmptyGuardBefore(block, statementIndex, name)` — у `block.body[0..statementIndex-1]` є `IfStatement`, який одночасно перевіряє «список порожній» (`isEmptyListTest`) і кидає (`consequentHasThrow`).
|
|
141
|
+
- `findEnclosingBlockAndStatementIndex(ancestors)` — у списку `ancestors` (зверху-вниз) знаходить найближчу пару `(BlockStatement, indexUnderItsBody)`, де statement з `ancestors[i]` входить у `block.body`.
|
|
142
|
+
|
|
143
|
+
#### Розпізнавання AST-патернів
|
|
144
|
+
|
|
145
|
+
- `isNewConnectionPool(node)` — `new {sql|mssql}.ConnectionPool(...)`.
|
|
146
|
+
- `isUnsafeQueryCallWithTemplateLiteral(node)` — `<obj>.query(\`...\`)`: метод `query`(некомпʼютований), перший аргумент —`TemplateLiteral`.
|
|
147
|
+
- `isRequestFactoryCall(node)` — `<obj>.request()`: будь-яке `CallExpression` з `.request` як методом (некомпʼютованим).
|
|
148
|
+
|
|
149
|
+
#### Гарантії числовості значень для `IN (${...})`
|
|
150
|
+
|
|
151
|
+
- `isLiteralNumericArrayExpression(node)` — `ArrayExpression`, всі елементи якого — `NumericLiteral`/`BigIntLiteral` або generic `Literal` зі значенням типу `number`/`bigint`. Масив має бути непорожнім.
|
|
152
|
+
- `isNumericParseCallExpression(node)` — `CallExpression`, де `callee` — це `Identifier` з імʼям з `NUMERIC_PARSE_FN_NAMES` = `{ parseInt, parseFloat, Number, BigInt }`, або `MemberExpression` з властивістю з того ж списку (наприклад `Number.parseInt(...)`).
|
|
153
|
+
- `subtreeHasNumericParseCall(node)` — рекурсивно обходить піддерево й повертає `true`, якщо знайшов `isNumericParseCallExpression` або `UnaryExpression` з оператором `+`. Поле `parent` пропускається, щоб уникнути нескінченних циклів.
|
|
154
|
+
- `isInListExpressionParsed(expr, declarators, seen=Set)` — головний критерій «безпечно числовий вираз». Повертає `true`, якщо:
|
|
155
|
+
- `expr` — літеральний масив чисел;
|
|
156
|
+
- або в піддереві `expr` є числовий парсер / унарний `+`;
|
|
157
|
+
- або `expr` — `Identifier`, у якого знайдено `VariableDeclarator`-и у файлі, і **кожен** `init` цих декларацій рекурсивно проходить ту ж перевірку. `seen` — анти-цикл (Set уже трасованих імен). Якщо для Identifier немає видимого init (наприклад параметр функції чи import) — повертається `false`.
|
|
158
|
+
|
|
159
|
+
#### Збирачі порушень для `IN (...)` сканерів
|
|
160
|
+
|
|
161
|
+
- `collectVariableDeclarators(programNode)` — обхід AST і збір усіх `VariableDeclarator`-ів.
|
|
162
|
+
- `quasiRawText(q)` — повертає `q.value.raw` або `''`, якщо структура `q` не підходить (захист від нестандартних AST-вузлів).
|
|
163
|
+
- `collectInListUnparsedFromTemplate(node, content, declarators, out)` — для одного `TemplateLiteral`: для кожного `expressions[i]` перевіряє, що `quasis[i].value.raw` закінчується на `IN (` (`IN_PLACEHOLDER_END_RE`), й експресія не є `.join(...)` і не «парсована». Якщо так — додає `{ line, snippet }` у `out`.
|
|
164
|
+
- `collectInListMissingEmptyGuardFromTemplate(node, ancestors, content, out)` — для одного `TemplateLiteral`: для кожного `expressions[i]` після `IN (`:
|
|
165
|
+
- якщо `expr` — не Identifier → порушення `not_var`;
|
|
166
|
+
- якщо `expr` — Identifier, але в enclosing-блоці немає guard перед поточним statement → порушення `missing_guard` з `name`.
|
|
167
|
+
|
|
168
|
+
### Side effects по модулю в цілому
|
|
169
|
+
|
|
170
|
+
Жодна функція не звертається до файлової системи / мережі та не мутує вхідні дані; усе — pure-функції над рядками й AST. Парсинг `parseSync` із `oxc-parser` — синхронний, його винятки ловляться, і у разі помилки повертається `[]`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Залежності
|
|
175
|
+
|
|
176
|
+
### Зовнішні npm-залежності
|
|
177
|
+
|
|
178
|
+
- **`oxc-parser`** — імпорт `parseSync`. Швидкий парсер JS/TS (Rust-based) для побудови AST з вибором мови за `lang` і `sourceType: 'module'`.
|
|
179
|
+
|
|
180
|
+
### Внутрішні залежності проєкту
|
|
181
|
+
|
|
182
|
+
Усі імпортуються з відносного шляху `../../../scripts/utils/ast-scan-utils.mjs`:
|
|
183
|
+
|
|
184
|
+
- `isFunctionNode(node)` — чи `node` є функціональним AST-вузлом (FunctionDeclaration / FunctionExpression / ArrowFunctionExpression / тощо).
|
|
185
|
+
- `isJoinCall(node)` — чи `node` — це `CallExpression` виду `<x>.join(<sep>)`.
|
|
186
|
+
- `isSqlListContextTemplate(template)` — чи `TemplateLiteral` знаходиться у SQL-контексті списку (`IN (...)` / `VALUES (...)`).
|
|
187
|
+
- `langFromPath(path)` — обчислює `lang` для парсера за розширенням файлу (наприклад `'js'`, `'ts'`, `'tsx'`).
|
|
188
|
+
- `normalizeSnippet(text)` — нормалізує сирий фрагмент коду до однорядкового сніппета (підрізає пробіли/нові рядки).
|
|
189
|
+
- `offsetToLine(content, offset)` — конвертує абсолютний offset у вихідному коді в 1-based номер рядка.
|
|
190
|
+
- `walkAstWithAncestors(root, initialAncestors, visitor)` — обхід AST із трекінгом `ancestors` для кожного вузла.
|
|
191
|
+
|
|
192
|
+
### Константи модуля
|
|
193
|
+
|
|
194
|
+
- `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/` — фільтр розширень сорсів.
|
|
195
|
+
- `IN_PLACEHOLDER_END_RE = /\bin\s*\(\s*$/iu` — детекція `... IN ( ` у raw-тексті quasi.
|
|
196
|
+
- `NUMERIC_PARSE_FN_NAMES = new Set(['parseInt', 'parseFloat', 'Number', 'BigInt'])` — імена-«парсери чисел».
|
|
197
|
+
- `EMPTY_LIST_BINARY_OPERATORS = new Set(['===', '==', '<=', '<'])` — оператори у тесті `name.length OP 0`.
|
|
198
|
+
- `EMPTY_LIST_REVERSED_OPERATORS = new Set(['===', '=='])` — оператори, для яких дозволено зворотний порядок `0 OP name.length`.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Потік виконання / Використання
|
|
203
|
+
|
|
204
|
+
### Спільна каркасна логіка сканера
|
|
205
|
+
|
|
206
|
+
Усі сканери побудовані однаково:
|
|
207
|
+
|
|
208
|
+
1. Викликати `langFromPath(virtualPath || 'scan.ts')` → отримати `lang`.
|
|
209
|
+
2. Викликати `parseSync(virtualPath, content, { lang, sourceType: 'module' })` у `try/catch`. У `catch` → `return []`.
|
|
210
|
+
3. Якщо `result.errors?.length` → `return []` (парсер повернув помилки).
|
|
211
|
+
4. Викликати `walkAstWithAncestors(result.program, [], visitor)` з тим чи іншим visitor.
|
|
212
|
+
5. Повернути накопичений масив `out`.
|
|
213
|
+
|
|
214
|
+
### Особливості окремих сканерів
|
|
215
|
+
|
|
216
|
+
- **`findMssqlPerRequestConnectionInText`** додатково перевіряє, що поточний вузол знаходиться всередині функції: `ancestors.some(isFunctionNode)`. Без цього global-declaration `const pool = new sql.ConnectionPool(...)` помилково попадало б у порушення (а це якраз бажаний патерн).
|
|
217
|
+
- **`findUnsafeMssqlInListUnparsedInText`** перед обходом збирає всі `VariableDeclarator` (`collectVariableDeclarators`), щоб у `isInListExpressionParsed` мати можливість трасувати Identifier → init (рекурсивно). `seen` Set захищає від циклів виду `let a = b; let b = a;`.
|
|
218
|
+
- **`findUnsafeMssqlInListMissingEmptyGuardInText`** використовує `ancestors` усередині visitor: `findEnclosingBlockAndStatementIndex(ancestors)` дає пару `(block, statementIndex)`, після чого `hasEmptyGuardBefore(block, statementIndex, name)` перевіряє наявність guard у тому ж блоці **до** statement, що містить запит.
|
|
219
|
+
|
|
220
|
+
### Як викликати
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
import {
|
|
224
|
+
findMssqlPerRequestConnectionInText,
|
|
225
|
+
findSharedMssqlRequestInText,
|
|
226
|
+
findUnsafeMssqlDynamicSqlListInText,
|
|
227
|
+
findUnsafeMssqlInListMissingEmptyGuardInText,
|
|
228
|
+
findUnsafeMssqlInListUnparsedInText,
|
|
229
|
+
findUnsafeMssqlQueryTemplateCallInText,
|
|
230
|
+
isMssqlScanSourceFile
|
|
231
|
+
} from './mssql-pool-scan.mjs'
|
|
232
|
+
|
|
233
|
+
import { readFileSync } from 'node:fs'
|
|
234
|
+
|
|
235
|
+
const relPath = 'pkg/src/db.ts'
|
|
236
|
+
if (!isMssqlScanSourceFile(relPath)) return
|
|
237
|
+
|
|
238
|
+
const content = readFileSync(relPath, 'utf8')
|
|
239
|
+
|
|
240
|
+
const violationsPool = findMssqlPerRequestConnectionInText(content, relPath)
|
|
241
|
+
const violationsQuery = findUnsafeMssqlQueryTemplateCallInText(content, relPath)
|
|
242
|
+
const violationsShared = findSharedMssqlRequestInText(content, relPath)
|
|
243
|
+
const violationsJoin = findUnsafeMssqlDynamicSqlListInText(content, relPath)
|
|
244
|
+
const violationsUnparsed = findUnsafeMssqlInListUnparsedInText(content, relPath)
|
|
245
|
+
const violationsGuard = findUnsafeMssqlInListMissingEmptyGuardInText(content, relPath)
|
|
246
|
+
|
|
247
|
+
// Кожен елемент:
|
|
248
|
+
// { line, snippet } — для перших пʼяти
|
|
249
|
+
// { line, snippet, reason, name? } — для останнього
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Приклади того, що ловить кожен сканер
|
|
253
|
+
|
|
254
|
+
#### `findMssqlPerRequestConnectionInText`
|
|
255
|
+
|
|
256
|
+
Порушення:
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
/**
|
|
260
|
+
*
|
|
261
|
+
*/
|
|
262
|
+
export async function handler() {
|
|
263
|
+
const pool = new sql.ConnectionPool(config) // створення на кожен запит
|
|
264
|
+
await pool.connect()
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Не порушення (модульний singleton):
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const pool = new sql.ConnectionPool(config) // на рівні модуля
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### `findUnsafeMssqlQueryTemplateCallInText`
|
|
275
|
+
|
|
276
|
+
Порушення:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
await pool.request().query(`SELECT * FROM users WHERE id = ${userId}`)
|
|
280
|
+
// це звичайний CallExpression з TemplateLiteral → SQL injection
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Не порушення (tagged template):
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
await pool.request().query`SELECT * FROM users WHERE id = ${userId}`
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### `findSharedMssqlRequestInText`
|
|
290
|
+
|
|
291
|
+
Порушення:
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
export const request = pool.request()
|
|
295
|
+
// або
|
|
296
|
+
const request = somePool.request()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### `findUnsafeMssqlDynamicSqlListInText`
|
|
300
|
+
|
|
301
|
+
Порушення:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
await sql.query`SELECT * FROM t WHERE id IN (${ids.join(',')})`
|
|
305
|
+
// .join(',') у списку IN/VALUES
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### `findUnsafeMssqlInListUnparsedInText`
|
|
309
|
+
|
|
310
|
+
Порушення:
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
await sql.query`SELECT * FROM t WHERE id IN (${ids})`
|
|
314
|
+
// expr=ids, init=ids не пройшов parseInt/Number/BigInt/+
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Не порушення:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
const ids = rawIds.map(x => parseInt(x, 10)).filter(Number.isFinite)
|
|
321
|
+
await sql.query`SELECT * FROM t WHERE id IN (${ids})`
|
|
322
|
+
// у піддереві ids є parseInt → subtreeHasNumericParseCall === true
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### `findUnsafeMssqlInListMissingEmptyGuardInText`
|
|
326
|
+
|
|
327
|
+
Порушення (`not_var` — вираз не Identifier):
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
await sql.query`SELECT * FROM t WHERE id IN (${rawIds.map(Number)})`
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Порушення (`missing_guard` — немає `if (!ids.length) throw`):
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
/**
|
|
337
|
+
*
|
|
338
|
+
*/
|
|
339
|
+
async function load(ids) {
|
|
340
|
+
await sql.query`SELECT * FROM t WHERE id IN (${ids})`
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Не порушення:
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
async function load(ids) {
|
|
351
|
+
if (!ids.length) throw new Error('empty')
|
|
352
|
+
await sql.query`SELECT * FROM t WHERE id IN (${ids})`
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Контракти та обмеження
|
|
357
|
+
|
|
358
|
+
- На синтаксично некоректному файлі **усі** сканери мовчки повертають `[]` — це навмисна стратегія, щоб не блокувати CI на парсинг-помилках (їх виявить лінт).
|
|
359
|
+
- Імпорт-Identifier-и не вважаються «парсованими» (немає видимого `init`), тож `findUnsafeMssqlInListUnparsedInText` для `import { ids } from '...'` дасть порушення — це консервативна поведінка.
|
|
360
|
+
- `isFunctionNode`/`isJoinCall`/`isSqlListContextTemplate` визначені у `ast-scan-utils.mjs`; зміни їхньої поведінки впливають на семантику цього модуля.
|
|
361
|
+
- Параметр `virtualPath` керує тільки вибором мови парсера. Якщо передати `.tsx`, парсер прийме JSX/TSX-синтаксис; для `.js` — звичайний JS і так далі.
|
|
362
|
+
|
|
363
|
+
### Точки розширення
|
|
364
|
+
|
|
365
|
+
- Додати новий клас порушень → ще одна функція `findX...InText(content, virtualPath)` + один visitor для `walkAstWithAncestors`.
|
|
366
|
+
- Розширити перелік числових парсерів → додати імʼя до `NUMERIC_PARSE_FN_NAMES`.
|
|
367
|
+
- Розширити форми guard-у → доповнити `isEmptyListTest` / `EMPTY_LIST_BINARY_OPERATORS` (наприклад додати `length < 1` через спеціальну гілку, бо поточна реалізація з `isZeroLiteral` цей варіант не покриває).
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Rebuild Test
|
|
372
|
+
|
|
373
|
+
Файл `mssql-pool-scan.mjs` можна повністю відтворити з цієї документації за такими опорними точками:
|
|
374
|
+
|
|
375
|
+
- Модуль ESM (`.mjs`), імпортує `parseSync` із `oxc-parser` і 7 утиліт із `../../../scripts/utils/ast-scan-utils.mjs` (`isFunctionNode`, `isJoinCall`, `isSqlListContextTemplate`, `langFromPath`, `normalizeSnippet`, `offsetToLine`, `walkAstWithAncestors`).
|
|
376
|
+
- Експортує 7 функцій (6 сканерів + `isMssqlScanSourceFile`) з підписами, описаними в розділі **Експорти / API** і **Функції**.
|
|
377
|
+
- Внутрішні константи: `SOURCE_FILE_RE`, `IN_PLACEHOLDER_END_RE`, `NUMERIC_PARSE_FN_NAMES`, `EMPTY_LIST_BINARY_OPERATORS`, `EMPTY_LIST_REVERSED_OPERATORS` — з точними значеннями з розділу **Залежності → Константи модуля**.
|
|
378
|
+
- Каркас сканера: `try { parseSync(...) } catch { return [] }`, далі `if (result.errors?.length) return []`, далі `walkAstWithAncestors(result.program, [], visitor)`, далі `return out`.
|
|
379
|
+
- Семантика visitor-ів — як описано в розділі **Функції → Експортовані функції** та **Збирачі порушень для `IN (...)` сканерів**.
|
|
380
|
+
- Допоміжні предикати (`isZeroLiteral`, `isLengthMemberOf`, `isEmptyListBinaryTest`, `isEmptyListTest`, `consequentHasThrow`, `hasEmptyGuardBefore`, `findEnclosingBlockAndStatementIndex`, `isNewConnectionPool`, `isUnsafeQueryCallWithTemplateLiteral`, `isRequestFactoryCall`, `isLiteralNumericArrayExpression`, `isNumericParseCallExpression`, `subtreeHasNumericParseCall`, `collectVariableDeclarators`, `quasiRawText`, `isInListExpressionParsed`, `collectInListUnparsedFromTemplate`, `collectInListMissingEmptyGuardFromTemplate`) — з контрактами, описаними в розділі **Приватні допоміжні функції**.
|