@nitra/cursor 3.22.0 → 3.23.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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/CHANGELOG.md +31 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +2 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# conn-file-rules.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль реалізує перевірки для файлів-підключень (connection files), що лежать у каталозі `src/conn/` (або в зоні дії lint-правила `conn-file` зі специфікації `js-run.mdc`, секції «Нейминг файлів у `src/conn/`» та «Експорти у файлах `src/conn/`»).
|
|
6
|
+
|
|
7
|
+
Він описує канонічну форму імені файла-підключення і канонічну форму його експорту, та надає функції для:
|
|
8
|
+
|
|
9
|
+
- визначення, чи файл взагалі підпадає під правило (за розширенням / типом);
|
|
10
|
+
- перевірки канонічного імені файла за регулярними виразами;
|
|
11
|
+
- утиліти `kebab-case → camelCase` для виведення очікуваного імені експорту з імені файла;
|
|
12
|
+
- статичного AST-аналізу через `oxc-parser` (обгорнутого в `parseProgramOrNull`) — пошук іменованих експортів і виявлення `export default`;
|
|
13
|
+
- збору списку порушень (`name` — невірне імʼя файла, `default-export` — наявність `export default`, `export-name` — відсутність очікуваного іменованого експорту).
|
|
14
|
+
|
|
15
|
+
Канонічні шаблони імен файлів:
|
|
16
|
+
|
|
17
|
+
- GraphQL: `ql-<id>.{js|mjs|cjs|ts|mts|cts|jsx|tsx}`, де `<id>` — kebab-case ідентифікатор endpoint.
|
|
18
|
+
- PostgreSQL: `pg-{read|write}.{ext}` або `pg-{read|write}-<id>.{ext}` (для multi-БД).
|
|
19
|
+
- MySQL: `mysql-{read|write}.{ext}` або `mysql-{read|write}-<id>.{ext}`.
|
|
20
|
+
- MSSQL: `mssql-{read|write}.{ext}` або `mssql-{read|write}-<id>.{ext}`.
|
|
21
|
+
|
|
22
|
+
Канонічна форма експорту — рівно один іменований експорт, без `export default`. Імʼя константи має дорівнювати camelCase від basename файла (без розширення); наприклад, `pg-write-contract.mjs` → `pgWriteContract`.
|
|
23
|
+
|
|
24
|
+
Якщо файл синтаксично невалідний (oxc не зміг розібрати), `findConnFileRuleViolations` не вигадує помилок — повертає лише ті, які можна впевнено зафіксувати (зокрема, порушення імені файла), а помилки синтаксису залишає іншим перевіркам, щоб не дублювати діагностику.
|
|
25
|
+
|
|
26
|
+
## Експорти / API
|
|
27
|
+
|
|
28
|
+
Модуль експортує тільки named-exports (узгоджено з `n-js-lint` / `js-run.mdc`):
|
|
29
|
+
|
|
30
|
+
| Експорт | Тип | Призначення |
|
|
31
|
+
| -------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `isConnFileRulesSourceFile(relativePathPosix)` | `(string) => boolean` | Чи файл попадає під правило (JS/TS-сімʼя, без `.d.ts`). |
|
|
33
|
+
| `kebabToCamel(kebab)` | `(string) => string` | Перетворення kebab-case на camelCase. |
|
|
34
|
+
| `isConnFileNameValid(relativePathPosix)` | `(string) => boolean` | Чи basename файла відповідає одному з канонічних шаблонів (`ql-*`, `pg-*`, `mysql-*`, `mssql-*`). |
|
|
35
|
+
| `findConnFileRuleViolations(content, relativePathPosix)` | `(string, string) => Violation[]` | Повний прогін правил для конкретного файла. |
|
|
36
|
+
|
|
37
|
+
Тип `Violation` (інтенсіональний, не експортується явно):
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
{
|
|
41
|
+
kind: 'name' | 'default-export' | 'export-name',
|
|
42
|
+
expectedName?: string,
|
|
43
|
+
foundNames?: string[],
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `kind: 'name'` — basename файла не відповідає жодному канонічному шаблону.
|
|
48
|
+
- `kind: 'default-export'` — у файлі знайдено `export default ...`.
|
|
49
|
+
- `kind: 'export-name'` — серед named-експортів немає очікуваного `expectedName` (виведеного з імені файла); у `foundNames` — реальний список знайдених імен (для повідомлення linter-а).
|
|
50
|
+
|
|
51
|
+
## Функції
|
|
52
|
+
|
|
53
|
+
### `isConnFileRulesSourceFile(relativePathPosix)`
|
|
54
|
+
|
|
55
|
+
**Сигнатура:** `(relativePathPosix: string) => boolean`
|
|
56
|
+
|
|
57
|
+
**Параметри:**
|
|
58
|
+
|
|
59
|
+
- `relativePathPosix` — відносний posix-шлях файла від кореня пакета (наприклад, `src/conn/pg-write.mjs`).
|
|
60
|
+
|
|
61
|
+
**Повертає:** `true`, якщо файл має розширення зі множини `[cm]?[jt]sx?` (тобто `.js | .mjs | .cjs | .jsx | .ts | .mts | .cts | .tsx`) і не є файлом декларацій типів (`.d.ts`). Інакше — `false`.
|
|
62
|
+
|
|
63
|
+
**Side effects:** немає (pure).
|
|
64
|
+
|
|
65
|
+
**Примітки:** перевірка `endsWith('.d.ts')` навмисно стоїть після regex, бо `.d.ts` теж задовольняє `SOURCE_FILE_RE` (`.ts`), а тип-декларації не повинні скануватись як код-підключень.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### `kebabToCamel(kebab)`
|
|
70
|
+
|
|
71
|
+
**Сигнатура:** `(kebab: string) => string`
|
|
72
|
+
|
|
73
|
+
**Параметри:**
|
|
74
|
+
|
|
75
|
+
- `kebab` — рядок у kebab-case (`pg-write-contract`, `ql-list-users`).
|
|
76
|
+
|
|
77
|
+
**Повертає:** camelCase-варіант: усі послідовності `-<x>` (де `x ∈ [a-z0-9]`) замінюються на `X` (літера капіталізується). Не змінює провідну літеру: `pg-...` → `pg...` (тобто результат у lower-camelCase).
|
|
78
|
+
|
|
79
|
+
**Side effects:** немає (pure).
|
|
80
|
+
|
|
81
|
+
**Деталі:** реалізовано через `replaceAll(/-([a-z0-9])/gu, (_m, c) => c.toUpperCase())`. Для дефіса перед не-[a-z0-9] символом нічого не робиться, тому функція стійка до неочікуваних символів (вони залишаються як є).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `isConnFileNameValid(relativePathPosix)`
|
|
86
|
+
|
|
87
|
+
**Сигнатура:** `(relativePathPosix: string) => boolean`
|
|
88
|
+
|
|
89
|
+
**Параметри:**
|
|
90
|
+
|
|
91
|
+
- `relativePathPosix` — відносний posix-шлях файла.
|
|
92
|
+
|
|
93
|
+
**Повертає:** `true`, якщо basename (без шляху, з розширенням) відповідає одному з двох регулярних виразів:
|
|
94
|
+
|
|
95
|
+
- `CONN_FILENAME_QL_RE` — `^ql-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[cm]?[jt]sx?$`;
|
|
96
|
+
- `CONN_FILENAME_DB_RE` — `^(?:pg|mysql|mssql)-(?:read|write)(?:-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)?\.[cm]?[jt]sx?$`.
|
|
97
|
+
|
|
98
|
+
**Side effects:** немає (pure).
|
|
99
|
+
|
|
100
|
+
**Чому два regex:** обидва шаблони мають однакову `<id>`-частину, але GraphQL-форма не має `read|write` — обʼєднати їх у єдиний regex без зростання комплексності (sonarjs/regex-complexity) важко, тому розділили навмисно.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### `basenameNoExt(relativePathPosix)` _(внутрішня)_
|
|
105
|
+
|
|
106
|
+
**Сигнатура:** `(relativePathPosix: string) => string`
|
|
107
|
+
|
|
108
|
+
**Параметри:**
|
|
109
|
+
|
|
110
|
+
- `relativePathPosix` — posix-шлях файла.
|
|
111
|
+
|
|
112
|
+
**Повертає:** basename без розширення. Якщо у шляху немає `/`, обробляється весь рядок. Якщо немає `.` у basename (`dot <= 0`) — повертає basename як є. Це коректно для імен на кшталт `.env` (там `dot === 0`, ext не відрізаємо).
|
|
113
|
+
|
|
114
|
+
**Side effects:** немає.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `namesFromVariableDeclaration(decl)` _(внутрішня)_
|
|
119
|
+
|
|
120
|
+
**Сигнатура:** `(decl: Record<string, unknown>) => string[]`
|
|
121
|
+
|
|
122
|
+
**Параметри:**
|
|
123
|
+
|
|
124
|
+
- `decl` — AST-вузол `VariableDeclaration` (тіло `export const/let/var ...`).
|
|
125
|
+
|
|
126
|
+
**Повертає:** масив імен усіх декларованих змінних, чий `id.type === 'Identifier'`. Підтримує множинні declarators в одному `export const a, b = 1`. Деструктурізації `export const { x } = ...` _не_ підтримуються — їхні `id` не є `Identifier`, тож пропускаються (зазвичай у conn-файлах їх немає).
|
|
127
|
+
|
|
128
|
+
**Side effects:** немає.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### `nameFromFnOrClassDeclaration(decl)` _(внутрішня)_
|
|
133
|
+
|
|
134
|
+
**Сигнатура:** `(decl: Record<string, unknown>) => string | null`
|
|
135
|
+
|
|
136
|
+
**Параметри:**
|
|
137
|
+
|
|
138
|
+
- `decl` — AST-вузол `FunctionDeclaration` або `ClassDeclaration`, що йде як `declaration` у `ExportNamedDeclaration`.
|
|
139
|
+
|
|
140
|
+
**Повертає:** імʼя функції/класу або `null`, якщо це не FunctionDeclaration / ClassDeclaration, або `id` відсутній/анонімний.
|
|
141
|
+
|
|
142
|
+
**Side effects:** немає.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `nameFromExportSpecifier(specifier)` _(внутрішня)_
|
|
147
|
+
|
|
148
|
+
**Сигнатура:** `(specifier: Record<string, unknown> | null | undefined) => string | null`
|
|
149
|
+
|
|
150
|
+
**Параметри:**
|
|
151
|
+
|
|
152
|
+
- `specifier` — AST `ExportSpecifier` (елемент масиву `specifiers` у `export { X }` / `export { X as Y }`).
|
|
153
|
+
|
|
154
|
+
**Повертає:** імʼя, під яким значення експортовано назовні:
|
|
155
|
+
|
|
156
|
+
- якщо `exported.type === 'Identifier'` і `exported.name` — рядок → повертає `exported.name`;
|
|
157
|
+
- інакше якщо `exported.value` — рядок (форма `export { x as "string-name" }` із TS/ESTree) → повертає `exported.value`;
|
|
158
|
+
- інакше `null`.
|
|
159
|
+
|
|
160
|
+
**Side effects:** немає.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### `namesFromNamedExport(rec)` _(внутрішня)_
|
|
165
|
+
|
|
166
|
+
**Сигнатура:** `(rec: Record<string, unknown>) => string[]`
|
|
167
|
+
|
|
168
|
+
**Параметри:**
|
|
169
|
+
|
|
170
|
+
- `rec` — AST `ExportNamedDeclaration`.
|
|
171
|
+
|
|
172
|
+
**Повертає:** масив імен, отриманих з цього експортного вузла:
|
|
173
|
+
|
|
174
|
+
1. якщо `rec.declaration` присутній — обробляє вкладену декларацію:
|
|
175
|
+
- `VariableDeclaration` → `namesFromVariableDeclaration`;
|
|
176
|
+
- `FunctionDeclaration` / `ClassDeclaration` → одиничний масив із `nameFromFnOrClassDeclaration` або `[]` для анонімних;
|
|
177
|
+
2. якщо `declaration === null` — проходить `specifiers` і збирає імена через `nameFromExportSpecifier`.
|
|
178
|
+
|
|
179
|
+
`export * from 'x'` (тип `ExportAllDeclaration`) сюди не попадає (фільтрується вище за `type !== 'ExportNamedDeclaration'`).
|
|
180
|
+
|
|
181
|
+
**Side effects:** немає.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### `collectNamedExportNames(program)` _(внутрішня)_
|
|
186
|
+
|
|
187
|
+
**Сигнатура:** `(program: unknown) => string[]`
|
|
188
|
+
|
|
189
|
+
**Параметри:**
|
|
190
|
+
|
|
191
|
+
- `program` — корінь AST (результат `parseProgramOrNull`).
|
|
192
|
+
|
|
193
|
+
**Повертає:** конкатенацію всіх імен named-експортів у `program.body`. Захищена від nullish/неoбʼєктних значень і відсутнього `body` — повертає `[]`.
|
|
194
|
+
|
|
195
|
+
**Side effects:** немає.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `hasDefaultExport(program)` _(внутрішня)_
|
|
200
|
+
|
|
201
|
+
**Сигнатура:** `(program: unknown) => boolean`
|
|
202
|
+
|
|
203
|
+
**Параметри:**
|
|
204
|
+
|
|
205
|
+
- `program` — корінь AST.
|
|
206
|
+
|
|
207
|
+
**Повертає:** `true`, якщо у `program.body` знайдено хоча б один вузол із `type === 'ExportDefaultDeclaration'`. Інакше — `false` (включно з випадками, коли AST неприйнятний).
|
|
208
|
+
|
|
209
|
+
**Side effects:** немає.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `findConnFileRuleViolations(content, relativePathPosix)`
|
|
214
|
+
|
|
215
|
+
**Сигнатура:**
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
(content: string, relativePathPosix: string) =>
|
|
219
|
+
{ kind: 'name' | 'default-export' | 'export-name',
|
|
220
|
+
expectedName?: string,
|
|
221
|
+
foundNames?: string[] }[]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Параметри:**
|
|
225
|
+
|
|
226
|
+
- `content` — вихідний код файла.
|
|
227
|
+
- `relativePathPosix` — відносний posix-шлях файла від кореня пакета.
|
|
228
|
+
|
|
229
|
+
**Повертає:** масив порушень (можливо порожній).
|
|
230
|
+
|
|
231
|
+
**Side effects:** немає (read-only по строці; `parseProgramOrNull` теж не має сайд-ефектів окрім CPU).
|
|
232
|
+
|
|
233
|
+
**Алгоритм:**
|
|
234
|
+
|
|
235
|
+
1. Якщо `isConnFileNameValid(relativePathPosix)` повертає `false` — додається порушення `{ kind: 'name' }`. У цьому випадку перевірка камелкейс-імені експорту далі **не виконується**, бо очікуване імʼя неоднозначне (фactually undefined behavior для нестандартного імені файла).
|
|
236
|
+
2. Парсимо `content` через `parseProgramOrNull(content, relativePathPosix)`:
|
|
237
|
+
- якщо повернувся `null` (синтаксис не зайшов) — повертаємо вже зібрані порушення (тільки `name`, якщо було);
|
|
238
|
+
3. Якщо `hasDefaultExport(program)` — додаємо `{ kind: 'default-export' }`.
|
|
239
|
+
4. Якщо серед уже накопичених порушень є `name` — повертаємо без перевірки імені експорту (див. п. 1).
|
|
240
|
+
5. Інакше виводимо очікуване імʼя експорту: `kebabToCamel(basenameNoExt(<basename з шляху>))`. Зверніть увагу — у виклику передається `relativePathPosix.slice(lastIndexOf('/') + 1)`, тобто basename з розширенням, а `basenameNoExt` далі ріже розширення.
|
|
241
|
+
6. Збираємо реальні named-експорти через `collectNamedExportNames(program)`. Якщо очікуваного `expectedName` серед них немає — додаємо `{ kind: 'export-name', expectedName, foundNames: names }`.
|
|
242
|
+
|
|
243
|
+
**Не перевіряється навмисно:** наявність `export default` фіксується незалежно від валідності імені файла (можна одночасно отримати `name` + `default-export`). Зайвість додаткових іменованих експортів (наприклад, два expected експорти) не вважається порушенням цим правилом — звіряється лише наявність очікуваного імені.
|
|
244
|
+
|
|
245
|
+
## Залежності
|
|
246
|
+
|
|
247
|
+
**Імпорти:**
|
|
248
|
+
|
|
249
|
+
- `parseProgramOrNull` з `../../../scripts/utils/ast-scan-utils.mjs` — обгортка над `oxc-parser`, повертає `null` для синтаксично невалідних файлів. Це єдина зовнішня залежність модуля.
|
|
250
|
+
|
|
251
|
+
**Стандартна бібліотека:** немає (тільки рядкові операції та regex).
|
|
252
|
+
|
|
253
|
+
**Споживачі (типовий профіль):** lint-правило `conn-file` у пакеті `npm/rules/js-run` (модуль вказує на правило `js-run.mdc`). Очікується, що зовнішній runner викликає `isConnFileRulesSourceFile` для відбору кандидатів і `findConnFileRuleViolations` для звіту.
|
|
254
|
+
|
|
255
|
+
## Потік виконання / Використання
|
|
256
|
+
|
|
257
|
+
Типовий цикл lint-runner:
|
|
258
|
+
|
|
259
|
+
1. Зібрати perfile-список через `git ls-files` або `glob` у каталозі `src/conn/`.
|
|
260
|
+
2. Для кожного файла перевірити `isConnFileRulesSourceFile(relativePath)`; якщо `false` — пропустити.
|
|
261
|
+
3. Прочитати вміст і викликати `findConnFileRuleViolations(content, relativePath)`.
|
|
262
|
+
4. Для кожного `Violation` сформувати читабельне повідомлення:
|
|
263
|
+
- `name` → «імʼя файла не відповідає шаблонам `ql-*` / `pg-{read|write}*` / `mysql-{read|write}*` / `mssql-{read|write}*`»;
|
|
264
|
+
- `default-export` → «у файлах `src/conn/` заборонено `export default`»;
|
|
265
|
+
- `export-name` → «очікуваний named-експорт `expectedName`, знайдено: `foundNames.join(', ')`».
|
|
266
|
+
|
|
267
|
+
**Приклад використання (Node.js / Bun):**
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
import { readFileSync } from 'node:fs'
|
|
271
|
+
import {
|
|
272
|
+
isConnFileRulesSourceFile,
|
|
273
|
+
findConnFileRuleViolations,
|
|
274
|
+
} from './conn-file-rules.mjs'
|
|
275
|
+
|
|
276
|
+
const rel = 'src/conn/pg-write-contract.mjs'
|
|
277
|
+
if (isConnFileRulesSourceFile(rel)) {
|
|
278
|
+
const content = readFileSync(rel, 'utf8')
|
|
279
|
+
const violations = findConnFileRuleViolations(content, rel)
|
|
280
|
+
for (const v of violations) {
|
|
281
|
+
console.log(v.kind, v.expectedName ?? '', v.foundNames ?? '')
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Для прикладу, якщо файл `src/conn/pg-write-contract.mjs` містить:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
export const pgWriteContract = createPool({ /* ... */ })
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
→ `violations === []`. Якщо ж замість `pgWriteContract` стоїть `export default createPool(...)`, отримаємо два порушення: `default-export` і `export-name` (бо очікуване імʼя `pgWriteContract` не знайдене).
|
|
293
|
+
|
|
294
|
+
**Граничні випадки:**
|
|
295
|
+
|
|
296
|
+
- Файл з `.d.ts` — `isConnFileRulesSourceFile` → `false`, перевірка не запускається.
|
|
297
|
+
- Файл із валідним іменем, але невалідним JS-синтаксисом — повертається тільки `default-export`/`export-name`-перевірок не буде (бо `program === null`), порушення імені теж не буде (бо імʼя валідне). Це навмисна делегація: помилка синтаксису репортнеться іншим linter-правилом.
|
|
298
|
+
- Файл із нестандартним іменем (наприклад, `src/conn/connection.mjs`) — повертається лише `{ kind: 'name' }`, навіть якщо там є `export default`. Це усвідомлена відмова, щоб не дублювати шум — спочатку треба виправити імʼя файла.
|
|
299
|
+
- `export *` — ігнорується (немає конкретного імені для звірки).
|
|
300
|
+
- `export { x as "string-literal" }` — string-form імені вилучається з `exported.value` (підтримується сучасний ESTree-формат).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# conn-imports-scan.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль реалізує AST-сканер для правила «Внутрішні аліаси» з `js-run.mdc`. Його завдання — знаходити в JS/TS-файлах ті імпорти, що створюють підключення до бази даних або зовнішнього GraphQL-сервісу, і які повинні жити **лише** в каталозі `conn` пакета (типово `src/conn/`). Решта коду пакета має споживати ці підключення через `pkg-import` `#conn/...`, оголошений у полі `imports` файла `package.json`.
|
|
6
|
+
|
|
7
|
+
Сканер ловить три типи імпортів:
|
|
8
|
+
|
|
9
|
+
- `import { SQL } from 'bun'` — named-специфікатор `SQL` з модуля `bun`;
|
|
10
|
+
- `import ... from 'mssql'` — будь-який імпорт із модуля `mssql`;
|
|
11
|
+
- `import { GraphQLClient } from '@nitra/graphql-request'` — named-специфікатор `GraphQLClient` з пакета `@nitra/graphql-request`.
|
|
12
|
+
|
|
13
|
+
Каталог `conn` визначається динамічно за полем `package.json#imports['#conn/*']`. Якщо запис відсутній або має невідомий формат — використовується дефолтне значення `src/conn`. Поле `imports` — це нативний для Node.js механізм pkg-aliases, той самий, що задокументований у правилі.
|
|
14
|
+
|
|
15
|
+
Семантика імпортів читається через **`oxc-parser`** (`module.staticImports`); regex по тілу файлу свідомо **не** використовується (щоб не плутати рядкові літерали, коментарі, динамічні `import()` тощо). Якщо файл не парситься (синтаксична помилка) — сканер повертає порожній список: спершу треба полагодити синтаксис, інакше будь-які звіти будуть нерелевантні.
|
|
16
|
+
|
|
17
|
+
Модуль не виконує жодних побічних дій (read-only): не читає файлову систему, не пише, не звертається до мережі. Працює виключно з переданими аргументами (вмістом файлу та результатом парсингу `package.json`).
|
|
18
|
+
|
|
19
|
+
## Експорти / API
|
|
20
|
+
|
|
21
|
+
Модуль експортує чотири публічні функції (іменовані експорти):
|
|
22
|
+
|
|
23
|
+
| Експорт | Тип | Призначення |
|
|
24
|
+
| ----------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `resolveConnDirFromPackageJson(pkgJson)` | function | Визначає відносний шлях до каталогу `conn` за `package.json#imports['#conn/*']` (з дефолтом `src/conn`). |
|
|
26
|
+
| `isInsideConnDir(relPosix, connDir)` | function | Перевіряє, чи лежить файл у каталозі `conn` (точно або вкладено). |
|
|
27
|
+
| `findConnFactoryImportsInText(content, virtualPath?)` | function | Знаходить у тексті JS/TS-файлу всі імпорти-«фабрики підключень», повертає список знахідок із позиціями. |
|
|
28
|
+
| `isConnImportsScanSourceFile(relativePathPosix)` | function | Фільтр: чи варто сканувати файл за розширенням (JS/TS-сім'я, виключно без `.d.ts`). |
|
|
29
|
+
|
|
30
|
+
Дві допоміжні функції (`stripTrailingSlashes`, `toPosixDir`) і одна класифікуюча (`classifyConnImport`) — приватні модульні (не експортуються).
|
|
31
|
+
|
|
32
|
+
На модульному рівні оголошена константа `SOURCE_FILE_RE` — regex для перевірки розширень:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
/\.([cm]?[jt]sx?)$/u
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Збігається з `.js`, `.cjs`, `.mjs`, `.jsx`, `.cjsx`, `.mjsx`, `.ts`, `.cts`, `.mts`, `.tsx`, `.ctsx`, `.mtsx`.
|
|
39
|
+
|
|
40
|
+
## Функції
|
|
41
|
+
|
|
42
|
+
### `stripTrailingSlashes(s)` (приватна)
|
|
43
|
+
|
|
44
|
+
- **Сигнатура:** `function stripTrailingSlashes(s: string): string`
|
|
45
|
+
- **Параметри:** `s` — рядок зі шляхом.
|
|
46
|
+
- **Повертає:** той самий рядок без хвостових `/`. Якщо хвостових слешів не було — повертає вхідне значення без копії (через `end === s.length`).
|
|
47
|
+
- **Алгоритм:** ітерує з кінця, поки кодова точка дорівнює `47` (символ `/` в ASCII), декрементує `end`. Реалізовано **без regex** свідомо — щоб уникнути попереджень про «slow regex» з боку лінтерів.
|
|
48
|
+
- **Side effects:** немає (pure).
|
|
49
|
+
|
|
50
|
+
### `toPosixDir(p)` (приватна)
|
|
51
|
+
|
|
52
|
+
- **Сигнатура:** `function toPosixDir(p: unknown): string`
|
|
53
|
+
- **Параметри:** `p` — вхідний шлях (може бути не-рядком; буде приведений через `String(p)`); допускає зворотні слеші (`\`) та префікс `./`.
|
|
54
|
+
- **Повертає:** нормалізований posix-шлях без хвостового `/`.
|
|
55
|
+
- **Алгоритм:**
|
|
56
|
+
1. Приводить до рядка через `String(p)`.
|
|
57
|
+
2. Усі `\` замінює на `/` (`replaceAll('\\', '/')`).
|
|
58
|
+
3. Тримінгує пробіли (`trim()`).
|
|
59
|
+
4. Якщо рядок починається з `./` — обрізає префікс.
|
|
60
|
+
5. Прибирає хвостові `/` через `stripTrailingSlashes`.
|
|
61
|
+
- **Side effects:** немає (pure).
|
|
62
|
+
|
|
63
|
+
### `resolveConnDirFromPackageJson(pkgJson)` (експорт)
|
|
64
|
+
|
|
65
|
+
- **Сигнатура:** `function resolveConnDirFromPackageJson(pkgJson: unknown): string`
|
|
66
|
+
- **Параметри:** `pkgJson` — будь-яке значення; зазвичай розпарсений `package.json` (об'єкт) або `null`. Функція стійка до невалідного вхідного типу.
|
|
67
|
+
- **Повертає:** відносний posix-шлях до каталогу `conn` без хвостового `/`. Дефолт — `'src/conn'`.
|
|
68
|
+
- **Алгоритм:**
|
|
69
|
+
1. Якщо `pkgJson` не об'єкт — повертає дефолт `'src/conn'`.
|
|
70
|
+
2. Бере поле `imports`. Якщо його немає або воно не об'єкт — повертає дефолт.
|
|
71
|
+
3. Шукає ключ `'#conn/*'`. Підтримує два формати запису:
|
|
72
|
+
- **Рядок:** `"#conn/*": "./src/conn/*"` — береться як є.
|
|
73
|
+
- **Об'єкт умовних експортів:** `{ default: '...', import: '...' }` — береться `default`, а якщо його немає — `import`.
|
|
74
|
+
4. Якщо знайдене значення не є рядком — повертає дефолт.
|
|
75
|
+
5. Нормалізує шлях через `toPosixDir`.
|
|
76
|
+
6. Якщо шлях закінчується на `/*` — обрізає ці два символи.
|
|
77
|
+
7. Прибирає хвостові слеші. Якщо в результаті порожній рядок — повертає дефолт.
|
|
78
|
+
- **Приклади:**
|
|
79
|
+
- `{ imports: { '#conn/*': './src/conn/*' } }` → `'src/conn'`.
|
|
80
|
+
- `{ imports: { '#conn/*': { default: 'lib/db/*' } } }` → `'lib/db'`.
|
|
81
|
+
- `{ imports: { '#conn/*': './src/conn/' } }` → `'src/conn'`.
|
|
82
|
+
- `null` або `{}` → `'src/conn'` (дефолт).
|
|
83
|
+
- **Side effects:** немає (pure).
|
|
84
|
+
|
|
85
|
+
### `isInsideConnDir(relPosix, connDir)` (експорт)
|
|
86
|
+
|
|
87
|
+
- **Сигнатура:** `function isInsideConnDir(relPosix: string, connDir: string): boolean`
|
|
88
|
+
- **Параметри:**
|
|
89
|
+
- `relPosix` — відносний posix-шлях до файлу;
|
|
90
|
+
- `connDir` — posix-шлях каталогу `conn` без хвостового `/`.
|
|
91
|
+
- **Повертає:** `true`, якщо файл лежить **точно** в каталозі `conn` або **вкладено** (тобто шлях починається з `${connDir}/`).
|
|
92
|
+
- **Алгоритм:**
|
|
93
|
+
1. Якщо `connDir` порожній/falsy — `false`.
|
|
94
|
+
2. Перевіряє точну рівність `relPosix === connDir` або початок з `${connDir}/`.
|
|
95
|
+
- **Зауваження:** функція не нормалізує `relPosix` — викликач повинен подавати вже нормалізований posix-шлях.
|
|
96
|
+
- **Side effects:** немає (pure).
|
|
97
|
+
|
|
98
|
+
### `classifyConnImport(staticImport)` (приватна)
|
|
99
|
+
|
|
100
|
+
- **Сигнатура:** `function classifyConnImport(staticImport: Record<string, unknown>): { module: string, specifier: string } | null`
|
|
101
|
+
- **Параметри:** `staticImport` — один елемент масиву `module.staticImports` з результату `oxc-parser`.
|
|
102
|
+
- **Повертає:** опис порушення `{ module, specifier }` або `null`, якщо це не «фабричний» імпорт підключення.
|
|
103
|
+
- **Алгоритм:**
|
|
104
|
+
1. Витягує назву модуля з `staticImport.moduleRequest?.value`; якщо не рядок — `null`.
|
|
105
|
+
2. Бере `entries` (named-специфікатори); якщо не масив — порожній список.
|
|
106
|
+
3. Розгалуження за іменем модуля:
|
|
107
|
+
- `'bun'`: шукає у `entries` запис із `importName.name === 'SQL'`. Повертає `{ module: 'bun', specifier: 'SQL' }`. Інакше — `null`.
|
|
108
|
+
- `'mssql'`: будь-який імпорт з цього модуля вважається порушенням; повертає `{ module: 'mssql', specifier: '*' }`. Тут `'*'` означає «будь-який специфікатор / default-імпорт», що відповідає JSDoc `import sql from 'mssql'` або інших форм.
|
|
109
|
+
- `'@nitra/graphql-request'`: шукає `entries` із `importName.name === 'GraphQLClient'`. Повертає `{ module, specifier: 'GraphQLClient' }`. Інакше — `null`.
|
|
110
|
+
- Інші модулі — `null`.
|
|
111
|
+
- **Side effects:** немає (pure).
|
|
112
|
+
|
|
113
|
+
### `findConnFactoryImportsInText(content, virtualPath?)` (експорт)
|
|
114
|
+
|
|
115
|
+
- **Сигнатура:** `function findConnFactoryImportsInText(content: string, virtualPath?: string): { line: number, snippet: string, module: string, specifier: string }[]`
|
|
116
|
+
- **Параметри:**
|
|
117
|
+
- `content` — вихідний код файлу;
|
|
118
|
+
- `virtualPath` — необов'язковий «віртуальний» шлях (наприклад `'pkg/src/index.ts'`), потрібен лише для визначення `lang` (мови парсера). Дефолт — `'scan.ts'`.
|
|
119
|
+
- **Повертає:** масив порушень. Кожен елемент:
|
|
120
|
+
- `line` — номер рядка з 1-based (через `offsetToLine`);
|
|
121
|
+
- `snippet` — нормалізований уривок коду імпорту (через `normalizeSnippet`);
|
|
122
|
+
- `module` — назва модуля (`'bun'` | `'mssql'` | `'@nitra/graphql-request'`);
|
|
123
|
+
- `specifier` — `'SQL'` | `'*'` | `'GraphQLClient'`.
|
|
124
|
+
- **Алгоритм:**
|
|
125
|
+
1. Визначає `lang` через `langFromPath(virtualPath || 'scan.ts')`.
|
|
126
|
+
2. Викликає `parseSync(virtualPath || 'scan.ts', content, { lang, sourceType: 'module' })`.
|
|
127
|
+
3. Якщо парсер кинув виняток — повертає `[]` (silently).
|
|
128
|
+
4. Якщо `result.errors` непорожній — теж повертає `[]` (некоректний синтаксис не сканується).
|
|
129
|
+
5. Ітерує `result.module?.staticImports ?? []`. Для кожного — `classifyConnImport`; якщо повернув `null`, пропускає.
|
|
130
|
+
6. Для збігу формує запис із `line` (через `offsetToLine(content, imp.start)`) і `snippet` (через `normalizeSnippet(content.slice(imp.start, imp.end))`).
|
|
131
|
+
- **Side effects:** немає; функція повністю pure щодо аргументів, але внутрішньо викликає `parseSync`, що може кидати.
|
|
132
|
+
|
|
133
|
+
### `isConnImportsScanSourceFile(relativePathPosix)` (експорт)
|
|
134
|
+
|
|
135
|
+
- **Сигнатура:** `function isConnImportsScanSourceFile(relativePathPosix: string): boolean`
|
|
136
|
+
- **Параметри:** `relativePathPosix` — відносний posix-шлях.
|
|
137
|
+
- **Повертає:** `true`, якщо файл має JS/TS-розширення (за `SOURCE_FILE_RE`) **і** не закінчується на `.d.ts` (декларації типів виключені).
|
|
138
|
+
- **Side effects:** немає (pure).
|
|
139
|
+
|
|
140
|
+
## Залежності
|
|
141
|
+
|
|
142
|
+
### Зовнішні (npm)
|
|
143
|
+
|
|
144
|
+
- **`oxc-parser`** — швидкий парсер JS/TS, з якого використовується іменований експорт `parseSync(filename, source, options)`. У результаті очікується структура з полем `module.staticImports` (масив static-імпортів з полями `moduleRequest.value`, `entries[].importName.name`, `start`, `end`) та полем `errors` (синтаксичні помилки парсера).
|
|
145
|
+
|
|
146
|
+
### Внутрішні (відносні)
|
|
147
|
+
|
|
148
|
+
- **`../../../scripts/utils/ast-scan-utils.mjs`** — спільні утиліти для AST-сканерів:
|
|
149
|
+
- `langFromPath(path)` — визначає мову парсера за розширенням (`'js'` | `'ts'` тощо).
|
|
150
|
+
- `normalizeSnippet(text)` — нормалізує сирий уривок коду для звітів (тримінг, прибирання зайвих пробілів/перенесень).
|
|
151
|
+
- `offsetToLine(content, offset)` — конвертує байтовий/символьний offset у номер рядка (зазвичай 1-based).
|
|
152
|
+
|
|
153
|
+
### Стандартна бібліотека / runtime
|
|
154
|
+
|
|
155
|
+
- Тільки рядкові методи (`String`, `replaceAll`, `slice`, `startsWith`, `endsWith`, `trim`, `codePointAt`) і `Array.isArray`. Жодного `fs`, `path`, `process`.
|
|
156
|
+
|
|
157
|
+
## Потік виконання / Використання
|
|
158
|
+
|
|
159
|
+
Модуль — це «бібліотека» для check-скрипта правила «Внутрішні аліаси» (зазвичай викликається з `check-<id>.mjs` в `npm/rules/js-run/`). Типовий сценарій використання:
|
|
160
|
+
|
|
161
|
+
1. **Резолв конфігурації пакета.** Викликач читає `package.json` пакета, парсить JSON і передає об'єкт у `resolveConnDirFromPackageJson(pkgJson)` → отримує рядок `connDir`, наприклад `'src/conn'`.
|
|
162
|
+
2. **Фільтр кандидатів.** Для кожного файлу пакета викликач отримує відносний posix-шлях `relPath` і пропускає через два фільтри:
|
|
163
|
+
- `isConnImportsScanSourceFile(relPath)` — лише JS/TS-розширення, без `.d.ts`;
|
|
164
|
+
- `!isInsideConnDir(relPath, connDir)` — файл **не** в каталозі `conn`. Файли в `conn` дозволено мати такі імпорти — це їх роль.
|
|
165
|
+
3. **AST-скан.** Для кандидатів читає вміст файлу і викликає `findConnFactoryImportsInText(content, relPath)`. Передача `relPath` як `virtualPath` важлива, щоб `langFromPath` правильно обрав `ts`/`tsx`/`js`.
|
|
166
|
+
4. **Звіт про порушення.** Кожен елемент результату — це окреме порушення з:
|
|
167
|
+
- номером рядка для вказівки в логах/PR-коментарях;
|
|
168
|
+
- snippet-уривком (для контексту);
|
|
169
|
+
- модулем і специфікатором (для людиночитного формулювання правила, наприклад: «`bun:SQL` має жити в `#conn/*`»).
|
|
170
|
+
|
|
171
|
+
### Поведінка у крайових випадках
|
|
172
|
+
|
|
173
|
+
- **Файл із синтаксичною помилкою** — повертається порожній масив. Сканер не намагається відновлюватися; правило вимагає спершу полагодити синтаксис, інакше будь-який AST-аналіз ненадійний.
|
|
174
|
+
- **Файл без імпортів** — порожній масив (`staticImports` буде або порожнім, або відсутнім; `?? []` дбає про обидва випадки).
|
|
175
|
+
- **`package.json` без `imports['#conn/*']`** — повертається дефолт `'src/conn'`.
|
|
176
|
+
- **`package.json` із умовним експортом** — береться `default`, fallback на `import`. Інші ключі (`require`, `node`, тощо) ігноруються — політика правила орієнтована на ESM.
|
|
177
|
+
- **Хвостові слеші / `./`-префікс / windows-слеші** в шляху `imports` — нормалізуються в posix без хвостового `/`.
|
|
178
|
+
- **Інші модулі** (наприклад `pg`, `mysql2`, `mongodb`) — **не** ловляться цим сканером. Список цільових модулів навмисно вузький: `bun`/`SQL`, `mssql`, `@nitra/graphql-request`/`GraphQLClient`. Розширення списку — окрема зміна правила.
|
|
179
|
+
- **Default-імпорт з `bun` чи `@nitra/graphql-request`** — не вважається порушенням, бо умова перевіряє named-специфікатор. А для `mssql` ловиться будь-яка форма імпорту.
|
|
180
|
+
|
|
181
|
+
### Зв'язок із правилом `js-run.mdc`
|
|
182
|
+
|
|
183
|
+
Цей файл — частина пакета `npm/rules/js-run/`, який імплементує правило «Внутрішні аліаси». Документ правила (`.mdc`) задає **що** заборонено (підключення до БД/GraphQL поза `src/conn`), а цей модуль — **як** це виявити статично через AST. Назви функцій (`isConnImportsScanSourceFile`, `findConnFactoryImportsInText`) узгоджені з конвенцією наіменування check-скриптів у `n-cursor`.
|
|
184
|
+
|
|
185
|
+
## Rebuild Test
|
|
186
|
+
|
|
187
|
+
Цей розділ описує, як перевірити, що документація відповідає коду. Якщо реалізація зміниться — оновити документ так, щоб виконувалися всі пункти нижче.
|
|
188
|
+
|
|
189
|
+
1. **Експорти.** Модуль експортує рівно 4 функції: `resolveConnDirFromPackageJson`, `isInsideConnDir`, `findConnFactoryImportsInText`, `isConnImportsScanSourceFile`. Жодних default-експортів.
|
|
190
|
+
2. **Дефолтний conn-каталог.** Для `null`, `{}`, `{ imports: {} }`, `{ imports: { '#conn/*': 42 } }` функція `resolveConnDirFromPackageJson` повертає `'src/conn'`.
|
|
191
|
+
3. **Парсинг conn-каталогу зі string-target.** Для `{ imports: { '#conn/*': './src/conn/*' } }` повертається `'src/conn'`; хвіст `/*` і префікс `./` зрізаються.
|
|
192
|
+
4. **Парсинг conn-каталогу з умовним експортом.** Для `{ imports: { '#conn/*': { default: 'lib/db/*' } } }` повертається `'lib/db'`. Якщо `default` відсутній, але є `import` — береться `import`.
|
|
193
|
+
5. **`isInsideConnDir`.** `('src/conn', 'src/conn')` → `true`; `('src/conn/db.ts', 'src/conn')` → `true`; `('src/conn-other/x.ts', 'src/conn')` → `false`; `('x', '')` → `false`.
|
|
194
|
+
6. **Фільтр розширень.** `isConnImportsScanSourceFile`:
|
|
195
|
+
- `'a.js'`, `'a.mjs'`, `'a.cjs'`, `'a.ts'`, `'a.mts'`, `'a.cts'`, `'a.jsx'`, `'a.tsx'` → `true`;
|
|
196
|
+
- `'a.d.ts'` → `false`;
|
|
197
|
+
- `'a.json'`, `'a.vue'`, `'a.md'`, `'README'` → `false`.
|
|
198
|
+
7. **AST-скан `bun`.** Текст `import { SQL } from 'bun'` дає одну знахідку з `module: 'bun'`, `specifier: 'SQL'`, `line: 1`.
|
|
199
|
+
8. **AST-скан `bun` без `SQL`.** Текст `import { serve } from 'bun'` дає порожній масив.
|
|
200
|
+
9. **AST-скан `mssql`.** Будь-яка форма (`import sql from 'mssql'`, `import * as mssql from 'mssql'`, `import 'mssql'`) дає одну знахідку з `module: 'mssql'`, `specifier: '*'`.
|
|
201
|
+
10. **AST-скан `@nitra/graphql-request`.** `import { GraphQLClient } from '@nitra/graphql-request'` дає знахідку з `specifier: 'GraphQLClient'`; інші named-імпорти з цього модуля — порожній результат.
|
|
202
|
+
11. **Синтаксична помилка.** Невалідний JS/TS (наприклад `import {{`) повертає порожній масив — без винятків назовні.
|
|
203
|
+
12. **`virtualPath` впливає лише на `lang`.** Виклик із `'x.ts'` парсить як TypeScript; з `'x.js'` — як JavaScript; з `'x.tsx'` — як TSX. Уривок коду в `snippet` — нормалізований через `normalizeSnippet`.
|
|
204
|
+
13. **Read-only.** Жодних викликів `fs`, `path`, `process`, мережі. Усі функції pure щодо своїх аргументів (єдиний нечистий аспект — `parseSync` усередині `findConnFactoryImportsInText`, який ловиться `try/catch`).
|