@7n/rules-lang-js 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -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 +31 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +89 -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,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевірки для файлів-підключень у каталозі `#conn` (js-run.mdc → «Нейминг файлів у `src/conn/`»
|
|
3
|
+
* та «Експорти у файлах `src/conn/`»).
|
|
4
|
+
*
|
|
5
|
+
* Канонічна назва файла:
|
|
6
|
+
* - GraphQL: `ql-<id>.{js|mjs|cjs|ts|mts|cts}` (id — kebab-case ідентифікатор endpoint);
|
|
7
|
+
* - PostgreSQL: `pg-{read|write}.{ext}` або `pg-{read|write}-<id>.{ext}` (id — для multi-БД);
|
|
8
|
+
* - MySQL: `mysql-{read|write}.{ext}` або `mysql-{read|write}-<id>.{ext}`;
|
|
9
|
+
* - MSSQL: `mssql-{read|write}.{ext}` або `mssql-{read|write}-<id>.{ext}`.
|
|
10
|
+
*
|
|
11
|
+
* Канонічний експорт — іменований, без `export default`. Імʼя константи має дорівнювати
|
|
12
|
+
* camelCase від basename файла (`pg-write-contract` → `pgWriteContract`).
|
|
13
|
+
*
|
|
14
|
+
* Парсимо через oxc-parser; коли файл не парситься — повертаємо порожні результати, щоб
|
|
15
|
+
* не змішувати помилки синтаксису з порушеннями цього правила.
|
|
16
|
+
*/
|
|
17
|
+
import { parseProgramOrNull } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
|
|
18
|
+
|
|
19
|
+
const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Канонічний шаблон імені GraphQL-файла: `ql-<id>.<ext>`.
|
|
23
|
+
* `<id>` — kebab без leading/trailing-`-`, починається/закінчується на `[a-z0-9]`.
|
|
24
|
+
*/
|
|
25
|
+
const CONN_FILENAME_QL_RE = /^ql-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[cm]?[jt]sx?$/u
|
|
26
|
+
/**
|
|
27
|
+
* Канонічний шаблон імені файла БД-підключення: `(pg|mysql|mssql)-(read|write)(-<id>)?.<ext>`.
|
|
28
|
+
* `<id>` — за тими ж правилами, що й для `ql-`. Розділили з GraphQL-формою, щоб
|
|
29
|
+
* не множити комплексність regex (sonarjs/regex-complexity).
|
|
30
|
+
*/
|
|
31
|
+
const CONN_FILENAME_DB_RE = /^(?:pg|mysql|mssql)-(?:read|write)(?:-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)?\.[cm]?[jt]sx?$/u
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Чи це файл, який сканується правилом «conn-file» (JS/TS-сімʼя, без `.d.ts`).
|
|
35
|
+
* @param {string} relativePathPosix відносний posix-шлях
|
|
36
|
+
* @returns {boolean} true, якщо потрібно перевіряти
|
|
37
|
+
*/
|
|
38
|
+
export function isConnFileRulesSourceFile(relativePathPosix) {
|
|
39
|
+
return SOURCE_FILE_RE.test(relativePathPosix) && !relativePathPosix.endsWith('.d.ts')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Витягує basename файла без розширення.
|
|
44
|
+
* @param {string} relativePathPosix відносний шлях у posix-форматі
|
|
45
|
+
* @returns {string} basename без розширення (наприклад, `pg-write-contract`)
|
|
46
|
+
*/
|
|
47
|
+
function basenameNoExt(relativePathPosix) {
|
|
48
|
+
const last = relativePathPosix.lastIndexOf('/')
|
|
49
|
+
const base = last === -1 ? relativePathPosix : relativePathPosix.slice(last + 1)
|
|
50
|
+
const dot = base.lastIndexOf('.')
|
|
51
|
+
return dot > 0 ? base.slice(0, dot) : base
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Перетворює kebab-case ідентифікатор у camelCase.
|
|
56
|
+
* @param {string} kebab kebab-case рядок (`pg-write-contract`)
|
|
57
|
+
* @returns {string} camelCase (`pgWriteContract`)
|
|
58
|
+
*/
|
|
59
|
+
export function kebabToCamel(kebab) {
|
|
60
|
+
return kebab.replaceAll(/-([a-z0-9])/gu, (_m, c) => c.toUpperCase())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Чи відповідає назва файла канонічному шаблону для каталогу conn.
|
|
65
|
+
* @param {string} relativePathPosix відносний posix-шлях файла
|
|
66
|
+
* @returns {boolean} true, якщо basename + ext збігається зі схемою
|
|
67
|
+
*/
|
|
68
|
+
export function isConnFileNameValid(relativePathPosix) {
|
|
69
|
+
const last = relativePathPosix.lastIndexOf('/')
|
|
70
|
+
const base = last === -1 ? relativePathPosix : relativePathPosix.slice(last + 1)
|
|
71
|
+
return CONN_FILENAME_QL_RE.test(base) || CONN_FILENAME_DB_RE.test(base)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Витягує імена з `export const/let/var X = …` (включно з кількома declarators у одному `export const a, b`).
|
|
76
|
+
* @param {Record<string, unknown>} decl AST `VariableDeclaration`
|
|
77
|
+
* @returns {string[]} імена змінних
|
|
78
|
+
*/
|
|
79
|
+
function namesFromVariableDeclaration(decl) {
|
|
80
|
+
if (!Array.isArray(decl.declarations)) return []
|
|
81
|
+
/** @type {string[]} */
|
|
82
|
+
const out = []
|
|
83
|
+
for (const d of decl.declarations) {
|
|
84
|
+
const id = /** @type {Record<string, unknown> | null} */ (d?.id ?? null)
|
|
85
|
+
if (id && id.type === 'Identifier' && typeof id.name === 'string') out.push(id.name)
|
|
86
|
+
}
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Витягує імʼя з `export function X` / `export class X`.
|
|
92
|
+
* @param {Record<string, unknown>} decl AST `FunctionDeclaration` або `ClassDeclaration`
|
|
93
|
+
* @returns {string | null} імʼя або `null`, якщо id-вузол анонімний
|
|
94
|
+
*/
|
|
95
|
+
function nameFromFnOrClassDeclaration(decl) {
|
|
96
|
+
if (decl.type !== 'FunctionDeclaration' && decl.type !== 'ClassDeclaration') return null
|
|
97
|
+
const id = /** @type {Record<string, unknown> | null} */ (decl.id ?? null)
|
|
98
|
+
if (!id || typeof id !== 'object') return null
|
|
99
|
+
return typeof id.name === 'string' ? id.name : null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Витягує експортоване імʼя з одного `ExportSpecifier` (`export { X }` / `export { X as Y }`).
|
|
104
|
+
* @param {Record<string, unknown> | null | undefined} specifier AST `ExportSpecifier`
|
|
105
|
+
* @returns {string | null} імʼя або `null`
|
|
106
|
+
*/
|
|
107
|
+
function nameFromExportSpecifier(specifier) {
|
|
108
|
+
const exported = /** @type {Record<string, unknown> | null} */ (specifier?.exported ?? null)
|
|
109
|
+
if (!exported) return null
|
|
110
|
+
if (exported.type === 'Identifier' && typeof exported.name === 'string') return exported.name
|
|
111
|
+
if (typeof exported.value === 'string') return exported.value
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Імена з одного `ExportNamedDeclaration` — або з вкладеного `declaration`, або зі списку `specifiers`.
|
|
117
|
+
* @param {Record<string, unknown>} rec AST `ExportNamedDeclaration`
|
|
118
|
+
* @returns {string[]} імена цього експортного вузла
|
|
119
|
+
*/
|
|
120
|
+
function namesFromNamedExport(rec) {
|
|
121
|
+
const decl = /** @type {Record<string, unknown> | null} */ (rec.declaration ?? null)
|
|
122
|
+
if (decl) {
|
|
123
|
+
if (decl.type === 'VariableDeclaration') return namesFromVariableDeclaration(decl)
|
|
124
|
+
const fnOrClass = nameFromFnOrClassDeclaration(decl)
|
|
125
|
+
return fnOrClass ? [fnOrClass] : []
|
|
126
|
+
}
|
|
127
|
+
if (!Array.isArray(rec.specifiers)) return []
|
|
128
|
+
/** @type {string[]} */
|
|
129
|
+
const out = []
|
|
130
|
+
for (const s of rec.specifiers) {
|
|
131
|
+
const name = nameFromExportSpecifier(/** @type {Record<string, unknown> | null} */ (s ?? null))
|
|
132
|
+
if (name) out.push(name)
|
|
133
|
+
}
|
|
134
|
+
return out
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Збирає всі імена named-експортів у програмі.
|
|
139
|
+
*
|
|
140
|
+
* Покриває: `export const/let/var X`, `export function X`, `export class X`,
|
|
141
|
+
* `export { X }`, `export { X as Y }` (повертає `Y`). `export *` ігнорується
|
|
142
|
+
* (немає конкретного імені для звірки), `export default` обробляється окремо.
|
|
143
|
+
* @param {unknown} program AST root
|
|
144
|
+
* @returns {string[]} список експортованих імен
|
|
145
|
+
*/
|
|
146
|
+
function collectNamedExportNames(program) {
|
|
147
|
+
/** @type {string[]} */
|
|
148
|
+
const out = []
|
|
149
|
+
if (!program || typeof program !== 'object') return out
|
|
150
|
+
const body = /** @type {Record<string, unknown>} */ (program).body
|
|
151
|
+
if (!Array.isArray(body)) return out
|
|
152
|
+
for (const node of body) {
|
|
153
|
+
if (!node || typeof node !== 'object') continue
|
|
154
|
+
const rec = /** @type {Record<string, unknown>} */ (node)
|
|
155
|
+
if (rec.type !== 'ExportNamedDeclaration') continue
|
|
156
|
+
out.push(...namesFromNamedExport(rec))
|
|
157
|
+
}
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Чи є в програмі `export default ...`.
|
|
163
|
+
* @param {unknown} program AST root
|
|
164
|
+
* @returns {boolean} true, якщо знайдено будь-який ExportDefaultDeclaration
|
|
165
|
+
*/
|
|
166
|
+
function hasDefaultExport(program) {
|
|
167
|
+
if (!program || typeof program !== 'object') return false
|
|
168
|
+
const body = /** @type {Record<string, unknown>} */ (program).body
|
|
169
|
+
if (!Array.isArray(body)) return false
|
|
170
|
+
for (const node of body) {
|
|
171
|
+
if (
|
|
172
|
+
node &&
|
|
173
|
+
typeof node === 'object' &&
|
|
174
|
+
/** @type {Record<string, unknown>} */ (node).type === 'ExportDefaultDeclaration'
|
|
175
|
+
) {
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Знаходить порушення правил для одного файла з каталогу conn.
|
|
184
|
+
*
|
|
185
|
+
* Якщо AST не парситься — повертає порожній масив (синтаксис падає в інших перевірках,
|
|
186
|
+
* не дублюємо).
|
|
187
|
+
* @param {string} content вихідний код файла
|
|
188
|
+
* @param {string} relativePathPosix відносний posix-шлях файла (від кореня пакета)
|
|
189
|
+
* @returns {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }[]} список порушень
|
|
190
|
+
*/
|
|
191
|
+
export function findConnFileRuleViolations(content, relativePathPosix) {
|
|
192
|
+
/** @type {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }[]} */
|
|
193
|
+
const out = []
|
|
194
|
+
if (!isConnFileNameValid(relativePathPosix)) {
|
|
195
|
+
out.push({ kind: 'name' })
|
|
196
|
+
// якщо назва нестандартна — далі звірку імені експорту не робимо (camelCase двозначний)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const program = parseProgramOrNull(content, relativePathPosix)
|
|
200
|
+
if (!program) return out
|
|
201
|
+
|
|
202
|
+
if (hasDefaultExport(program)) {
|
|
203
|
+
out.push({ kind: 'default-export' })
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (out.some(v => v.kind === 'name')) return out
|
|
207
|
+
|
|
208
|
+
const expected = kebabToCamel(basenameNoExt(relativePathPosix.slice(relativePathPosix.lastIndexOf('/') + 1)))
|
|
209
|
+
const names = collectNamedExportNames(program)
|
|
210
|
+
if (!names.includes(expected)) {
|
|
211
|
+
out.push({ kind: 'export-name', expectedName: expected, foundNames: names })
|
|
212
|
+
}
|
|
213
|
+
return out
|
|
214
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-сканер для правила «Внутрішні аліаси» (js-run.mdc).
|
|
3
|
+
*
|
|
4
|
+
* Імпорти, які створюють підключення до БД / зовнішнього GraphQL, мають жити в окремому
|
|
5
|
+
* файлі (за замовчуванням — `src/conn/`), а решта коду повинна споживати їх через
|
|
6
|
+
* pkg-import `#conn/...`. Ловимо такі імпорти в файлах поза каталогом «conn»:
|
|
7
|
+
* - `import { SQL } from 'bun'` (named специфікатор `SQL`);
|
|
8
|
+
* - `import sql from 'mssql'` або будь-який `import ... from 'mssql'`;
|
|
9
|
+
* - `import { GraphQLClient } from '@nitra/graphql-request'` (named `GraphQLClient`).
|
|
10
|
+
*
|
|
11
|
+
* Каталог «conn» визначається з поля `package.json#imports['#conn/*']` (якщо є —
|
|
12
|
+
* відрізаємо `*` і нормалізуємо), інакше дефолт — `src/conn`. Ключ `imports` у
|
|
13
|
+
* package.json — нативний для Node.js, той самий, що й у документі правила.
|
|
14
|
+
*
|
|
15
|
+
* Семантика береться з **oxc-parser** (`module.staticImports`); regex по тілу файлу не
|
|
16
|
+
* використовується. Якщо файл не парситься — повертаємо порожній результат, спочатку
|
|
17
|
+
* треба полагодити синтаксис.
|
|
18
|
+
*/
|
|
19
|
+
import { langFromPath, normalizeSnippet, offsetToLine } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
|
|
20
|
+
import { parseSync } from 'oxc-parser'
|
|
21
|
+
|
|
22
|
+
const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Прибирає хвостові `/` зі шляху без використання regex (щоб не тригерити slow-regex попередження).
|
|
26
|
+
* @param {string} s рядок зі шляхом
|
|
27
|
+
* @returns {string} `s` без хвостових `/`
|
|
28
|
+
*/
|
|
29
|
+
function stripTrailingSlashes(s) {
|
|
30
|
+
let end = s.length
|
|
31
|
+
while (end > 0 && s.codePointAt(end - 1) === 47) end--
|
|
32
|
+
return end === s.length ? s : s.slice(0, end)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Нормалізує шлях до posix без хвостових слешів.
|
|
37
|
+
* @param {string} p вхідний шлях (можливо з `./` або зворотними слешами)
|
|
38
|
+
* @returns {string} нормалізований posix-шлях без хвостового `/`
|
|
39
|
+
*/
|
|
40
|
+
function toPosixDir(p) {
|
|
41
|
+
let s = String(p).replaceAll('\\', '/').trim()
|
|
42
|
+
if (s.startsWith('./')) s = s.slice(2)
|
|
43
|
+
return stripTrailingSlashes(s)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Визначає каталог «conn» за `package.json#imports['#conn/*']`. Дефолт — `src/conn`.
|
|
48
|
+
* @param {unknown} pkgJson розпарсений package.json (або null)
|
|
49
|
+
* @returns {string} відносний posix-шлях до каталогу conn (без хвостового `/`)
|
|
50
|
+
*/
|
|
51
|
+
export function resolveConnDirFromPackageJson(pkgJson) {
|
|
52
|
+
const fallback = 'src/conn'
|
|
53
|
+
if (!pkgJson || typeof pkgJson !== 'object') return fallback
|
|
54
|
+
const imports = /** @type {Record<string, unknown>} */ (pkgJson).imports
|
|
55
|
+
if (!imports || typeof imports !== 'object') return fallback
|
|
56
|
+
const target = /** @type {Record<string, unknown>} */ (imports)['#conn/*']
|
|
57
|
+
/** @type {string | null} */
|
|
58
|
+
let raw = null
|
|
59
|
+
if (typeof target === 'string') raw = target
|
|
60
|
+
else if (target && typeof target === 'object') {
|
|
61
|
+
// умовний експорт: { default: '...', import: '...' }
|
|
62
|
+
const obj = /** @type {Record<string, unknown>} */ (target)
|
|
63
|
+
if (typeof obj.default === 'string') raw = obj.default
|
|
64
|
+
else if (typeof obj.import === 'string') raw = obj.import
|
|
65
|
+
}
|
|
66
|
+
if (!raw) return fallback
|
|
67
|
+
// Прибираємо хвіст `*`, потім слеші
|
|
68
|
+
let s = toPosixDir(raw)
|
|
69
|
+
if (s.endsWith('/*')) s = s.slice(0, -2)
|
|
70
|
+
return stripTrailingSlashes(s) || fallback
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Чи перебуває файл у каталозі conn (точно або вкладено).
|
|
75
|
+
* @param {string} relPosix відносний posix-шлях до файлу
|
|
76
|
+
* @param {string} connDir posix-шлях каталогу conn (без хвостового `/`)
|
|
77
|
+
* @returns {boolean} true, якщо файл у каталозі conn
|
|
78
|
+
*/
|
|
79
|
+
export function isInsideConnDir(relPosix, connDir) {
|
|
80
|
+
if (!connDir) return false
|
|
81
|
+
return relPosix === connDir || relPosix.startsWith(`${connDir}/`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Чи це порушення правила «Внутрішні аліаси» — імпорт зі стороннього модуля, що створює
|
|
86
|
+
* підключення (`bun` зі специфікатором `SQL`, будь-який імпорт з `mssql`, або
|
|
87
|
+
* `@nitra/graphql-request` зі специфікатором `GraphQLClient`).
|
|
88
|
+
* @param {Record<string, unknown>} staticImport елемент `module.staticImports` з oxc-parser
|
|
89
|
+
* @returns {{ module: string, specifier: string } | null} опис порушення або null
|
|
90
|
+
*/
|
|
91
|
+
function classifyConnImport(staticImport) {
|
|
92
|
+
const mod = staticImport.moduleRequest?.value
|
|
93
|
+
if (typeof mod !== 'string') return null
|
|
94
|
+
const entries = Array.isArray(staticImport.entries) ? staticImport.entries : []
|
|
95
|
+
|
|
96
|
+
if (mod === 'bun') {
|
|
97
|
+
for (const e of entries) {
|
|
98
|
+
const name = e?.importName?.name
|
|
99
|
+
if (name === 'SQL') return { module: mod, specifier: 'SQL' }
|
|
100
|
+
}
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
if (mod === 'mssql') {
|
|
104
|
+
return { module: mod, specifier: '*' }
|
|
105
|
+
}
|
|
106
|
+
if (mod === '@nitra/graphql-request') {
|
|
107
|
+
for (const e of entries) {
|
|
108
|
+
const name = e?.importName?.name
|
|
109
|
+
if (name === 'GraphQLClient') return { module: mod, specifier: 'GraphQLClient' }
|
|
110
|
+
}
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Знаходить імпорти-«фабрики підключень» у тексті файлу.
|
|
118
|
+
* @param {string} content вихідний код
|
|
119
|
+
* @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/index.ts`)
|
|
120
|
+
* @returns {{ line: number, snippet: string, module: string, specifier: string }[]} список порушень
|
|
121
|
+
*/
|
|
122
|
+
export function findConnFactoryImportsInText(content, virtualPath = 'scan.ts') {
|
|
123
|
+
const lang = langFromPath(virtualPath || 'scan.ts')
|
|
124
|
+
let result
|
|
125
|
+
try {
|
|
126
|
+
result = parseSync(virtualPath || 'scan.ts', content, { lang, sourceType: 'module' })
|
|
127
|
+
} catch {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
if (result.errors?.length) return []
|
|
131
|
+
|
|
132
|
+
/** @type {{ line: number, snippet: string, module: string, specifier: string }[]} */
|
|
133
|
+
const out = []
|
|
134
|
+
for (const imp of result.module?.staticImports ?? []) {
|
|
135
|
+
const hit = classifyConnImport(imp)
|
|
136
|
+
if (!hit) continue
|
|
137
|
+
out.push({
|
|
138
|
+
line: offsetToLine(content, imp.start),
|
|
139
|
+
snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
|
|
140
|
+
module: hit.module,
|
|
141
|
+
specifier: hit.specifier
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
return out
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Чи сканувати цей файл за розширенням (JS/TS-сім'я, без `.d.ts`).
|
|
149
|
+
* @param {string} relativePathPosix відносний шлях (posix)
|
|
150
|
+
* @returns {boolean} true, якщо розширення підходить для AST-скану
|
|
151
|
+
*/
|
|
152
|
+
export function isConnImportsScanSourceFile(relativePathPosix) {
|
|
153
|
+
return SOURCE_FILE_RE.test(relativePathPosix) && !relativePathPosix.endsWith('.d.ts')
|
|
154
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: bunyan-imports.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js-run/lib/bunyan-imports.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 2494f203
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Модуль `bunyan-imports.mjs` — це AST-сканер, який знаходить у вихідних файлах JavaScript / TypeScript заборонені імпорти й виклики модулів `@nitra/bunyan` та застарілого `bunyan`. Згідно з правилом `js-run.mdc` уся логіка структурованого логування у проєкті переведена на `@nitra/pino`, тому будь-яке згадування `bunyan`/`@nitra/bunyan` має бути замінене на `@nitra/pino`.
|
|
10
|
+
|
|
11
|
+
Ключові властивості:
|
|
12
|
+
|
|
13
|
+
- Семантика береться з **oxc-parser** (`module.staticImports`) — без regex по тілу файлу; це робить сканер стійким до коментарів, рядкових літералів та форматувань.
|
|
14
|
+
- На додачу до статичних `import`-ів сканер обходить AST програми і ловить:
|
|
15
|
+
- `require('@nitra/bunyan')` / `require('bunyan')` — для CommonJS;
|
|
16
|
+
- динамічний `import('@nitra/bunyan')` / `import('bunyan')` — для випадків асинхронного підключення в межах одного файлу.
|
|
17
|
+
- Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках парсера повертається **порожній** результат — спершу слід полагодити синтаксис, потім перезапустити перевірку.
|
|
18
|
+
- Декларації типів (`*.d.ts`) виключені з обходу через окремий хелпер.
|
|
19
|
+
|
|
20
|
+
Файл є частиною бібліотеки правила `js-run` й використовується чек-скриптами цього правила для сигналізації про порушення.
|
|
21
|
+
|
|
22
|
+
## Експорти / API
|
|
23
|
+
|
|
24
|
+
Модуль експортує три іменовані функції:
|
|
25
|
+
|
|
26
|
+
| Експорт | Призначення |
|
|
27
|
+
| ------------------------------------------------ | ----------------------------------------------------------- |
|
|
28
|
+
| `findBunyanImportsInText(content, virtualPath?)` | Основний AST-сканер тексту файлу — повертає масив порушень. |
|
|
29
|
+
| `isBunyanScanSourceFile(relativePath)` | Фільтр за розширенням файлу (JS/TS-сім'я). |
|
|
30
|
+
| `shouldSkipFileForBunyanScan(relativePosix)` | Виключення декларацій типів (`*.d.ts`). |
|
|
31
|
+
|
|
32
|
+
Внутрішні (не експортовані) константи:
|
|
33
|
+
|
|
34
|
+
- `SOURCE_FILE_RE` — `/\.([cm]?[jt]sx?)$/u` — регулярний вираз для розширень `.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`.
|
|
35
|
+
- `FORBIDDEN_MODULES` — `Set` з двох імен модулів: `'@nitra/bunyan'` і `'bunyan'`.
|
|
36
|
+
|
|
37
|
+
## Функції
|
|
38
|
+
|
|
39
|
+
### `findBunyanImportsInText(content, virtualPath = 'scan.ts')`
|
|
40
|
+
|
|
41
|
+
Основний сканер. Парсить переданий текст як модуль JS/TS і збирає всі місця, де згадуються заборонені модулі через статичний `import`, `require(...)` або динамічний `import(...)`.
|
|
42
|
+
|
|
43
|
+
- Сигнатура: `findBunyanImportsInText(content: string, virtualPath?: string) => { line: number, snippet: string, module: string }[]`
|
|
44
|
+
- Параметри:
|
|
45
|
+
- `content` (`string`) — повний вихідний код, який треба перевірити.
|
|
46
|
+
- `virtualPath` (`string`, опційно, за замовчуванням `'scan.ts'`) — віртуальний шлях, який передається в парсер; використовується тільки для вибору `lang` (TS/JS/JSX/TSX) через `langFromPath(...)`. Реальний файл на диску не читається.
|
|
47
|
+
- Повертає: масив об'єктів-порушень. Кожен елемент:
|
|
48
|
+
- `line` — номер рядка (1-індекс) місця початку конструкції (`offsetToLine`).
|
|
49
|
+
- `snippet` — нормалізований текстовий зріз `content.slice(start, end)` через `normalizeSnippet` (стискання пробілів/обрізання).
|
|
50
|
+
- `module` — рядок самого імпортованого імені (`'@nitra/bunyan'` або `'bunyan'`).
|
|
51
|
+
- Порядок результату: спершу всі статичні `import`-и в порядку їх появи в `module.staticImports`, далі — `require`/`import(...)` у порядку обходу AST через `walkAstWithAncestors`.
|
|
52
|
+
- Side effects: відсутні. Функція суто функціональна: жодних I/O, мутацій вхідних параметрів чи логів. Виняткова поведінка `parseSync` поглинається `try/catch`, помилки парсера в `result.errors` трактуються як «нічого не знайдено» — повертається `[]`.
|
|
53
|
+
- Граничні випадки:
|
|
54
|
+
- `virtualPath` falsy (порожній рядок, `undefined`) — використовується запасне значення `'scan.ts'`.
|
|
55
|
+
- Якщо `parseSync` кинув виняток або у `result.errors` є записи — повертається `[]`.
|
|
56
|
+
- Якщо `result.module` відсутній (наприклад, скрипт CJS), цикл по `module.staticImports` пропускається через `?? []`, AST-обхід усе одно виконується.
|
|
57
|
+
- Для `require`/динамічного `import` модулі з не-літеральним аргументом (`require(name)`) хелпери з `ast-scan-utils` повернуть `null` і вузол буде проігноровано.
|
|
58
|
+
|
|
59
|
+
### `isBunyanScanSourceFile(relativePath)`
|
|
60
|
+
|
|
61
|
+
Швидкий фільтр за розширенням — чи має сенс взагалі парсити цей файл.
|
|
62
|
+
|
|
63
|
+
- Сигнатура: `isBunyanScanSourceFile(relativePath: string) => boolean`
|
|
64
|
+
- Параметри:
|
|
65
|
+
- `relativePath` (`string`) — відносний шлях до файлу (формат розділювача не важливий, перевіряється тільки суфікс).
|
|
66
|
+
- Повертає: `true`, якщо розширення входить у JS/TS-сім'ю (`.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`); інакше `false`.
|
|
67
|
+
- Side effects: відсутні; чиста перевірка регулярним виразом `SOURCE_FILE_RE`.
|
|
68
|
+
|
|
69
|
+
### `shouldSkipFileForBunyanScan(relativePosix)`
|
|
70
|
+
|
|
71
|
+
Виключення для декларацій типів TypeScript.
|
|
72
|
+
|
|
73
|
+
- Сигнатура: `shouldSkipFileForBunyanScan(relativePosix: string) => boolean`
|
|
74
|
+
- Параметри:
|
|
75
|
+
- `relativePosix` (`string`) — шлях у posix-форматі (з `/`).
|
|
76
|
+
- Повертає: `true`, якщо шлях завершується на `.d.ts` — такий файл містить лише типи й не повинен сканеритися; інакше `false`.
|
|
77
|
+
- Side effects: відсутні.
|
|
78
|
+
- Примітка: цей хелпер логічно йде паралельно до `isBunyanScanSourceFile`. Файл `*.d.ts` теж задовольнить `SOURCE_FILE_RE`, тож обхідник пакета має застосовувати **обидва** хелпери: спочатку відсіяти за розширенням, потім додатково пропустити `.d.ts`.
|
|
79
|
+
|
|
80
|
+
## Залежності
|
|
81
|
+
|
|
82
|
+
Зовнішні (`node_modules`):
|
|
83
|
+
|
|
84
|
+
- `oxc-parser` — парсер JS/TS, експортує `parseSync`. Сканер користується вузлами `module.staticImports` (метадані статичних `import`-декларацій) та повним AST програми (`result.program`).
|
|
85
|
+
|
|
86
|
+
Внутрішні (відносний імпорт з `../../../scripts/utils/ast-scan-utils.mjs`):
|
|
87
|
+
|
|
88
|
+
- `dynamicImportModule(node)` — повертає рядок-аргумент динамічного `import('...')` або `null`.
|
|
89
|
+
- `langFromPath(path)` — визначає `lang` (`'ts'`, `'tsx'`, `'js'`, `'jsx'`, …) за розширенням шляху для `parseSync`.
|
|
90
|
+
- `normalizeSnippet(text)` — нормалізує сирий шматок коду перед збереженням у результат (типово стискає пробіли / обрізає).
|
|
91
|
+
- `offsetToLine(content, offset)` — перетворює числовий offset у тексті на 1-індексований номер рядка.
|
|
92
|
+
- `requireCallModule(node)` — повертає рядок-аргумент виклику `require('...')` або `null`.
|
|
93
|
+
- `walkAstWithAncestors(program, ancestors, visitor)` — обхід AST з accumulator-предків (тут передається порожній масив; колбек дивиться лише на поточний `node`).
|
|
94
|
+
|
|
95
|
+
Очікувані виклики (хто використовує цей модуль): чек-скрипти правила `js-run` (наприклад, `npm/rules/js-run/check-*.mjs`), які отримують вміст файлів пакета й передають їх у `findBunyanImportsInText`, після чого формують повідомлення про порушення з `line` / `snippet` / `module`. Обхідник файлів пакета використовує `isBunyanScanSourceFile` для фільтра кандидатів і `shouldSkipFileForBunyanScan` для виключень.
|
|
96
|
+
|
|
97
|
+
## Потік виконання / Використання
|
|
98
|
+
|
|
99
|
+
Типовий сценарій інтеграції в чек-правила:
|
|
100
|
+
|
|
101
|
+
1. Обхідник перебирає всі файли пакета та конвертує шляхи у posix-формат.
|
|
102
|
+
2. Для кожного `relativePosix` викликається `isBunyanScanSourceFile(relativePosix)`. Якщо `false` — файл пропускається.
|
|
103
|
+
3. Якщо `shouldSkipFileForBunyanScan(relativePosix)` повертає `true` (тобто `*.d.ts`) — файл також пропускається.
|
|
104
|
+
4. Інакше файл читається з диска (зовнішнім кодом), а його вміст подається у `findBunyanImportsInText(content, relativePosix)`.
|
|
105
|
+
5. Усередині `findBunyanImportsInText`:
|
|
106
|
+
1. Обчислюється `lang` через `langFromPath(virtualPath)`.
|
|
107
|
+
2. Викликається `parseSync(virtualPath, content, { lang, sourceType: 'module' })` всередині `try/catch`.
|
|
108
|
+
3. Якщо парсер кинув виняток або в `result.errors` є записи — повертається `[]`.
|
|
109
|
+
4. Інакше перебираються `result.module?.staticImports ?? []`; для кожного `import`, де `moduleRequest.value` входить у `FORBIDDEN_MODULES`, до результату додається запис `{ line, snippet, module }`.
|
|
110
|
+
5. Далі `walkAstWithAncestors(result.program, [], visitor)` проходить по AST. У відвідувача:
|
|
111
|
+
- `requireCallModule(node)` повертає не-`null` для `require('...')` — якщо модуль заборонений, додаємо запис; інакше пропускаємо.
|
|
112
|
+
- `dynamicImportModule(node)` — те саме для динамічного `import('...')`.
|
|
113
|
+
6. Викликач отримує плаский масив порушень і формує звіт (наприклад, друкує `path:line` та `snippet`).
|
|
114
|
+
|
|
115
|
+
Гарантії та особливості:
|
|
116
|
+
|
|
117
|
+
- Чисто синхронний прохід — жодного I/O всередині модуля.
|
|
118
|
+
- Якщо файл містить синтаксичні помилки, результат `[]` означає **не «все добре»**, а «парсер не зрозумів файл» — це усвідомлене дизайн-рішення: спершу виправити синтаксис, потім перезапустити перевірку.
|
|
119
|
+
- Регулярка `SOURCE_FILE_RE` не покриває нестандартні розширення (наприклад, `.vue`, `.svelte`). Якщо для них потрібен власний пайплайн (з вилученням `<script>` блоку), його треба будувати окремо.
|
|
120
|
+
- Між списком `module.staticImports` і обходом AST вузли `import` не дублюються: статичні `import`-декларації обходяться лише через `module.staticImports`, а у візиторі перевіряються тільки `require` та динамічний `import(...)`.
|
|
121
|
+
- Перелік заборонених модулів суворо обмежений `FORBIDDEN_MODULES` (рівність рядків). Підшляхи на кшталт `@nitra/bunyan/foo` не вловлюються — це навмисна вузька семантика правила.
|