@nitra/cursor 1.8.212 → 1.8.216

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.
@@ -100,7 +100,7 @@ function collectNamedExportNames(program) {
100
100
  (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') &&
101
101
  decl.id &&
102
102
  typeof decl.id === 'object' &&
103
- typeof /** @type {Record<string, unknown>} */ (decl.id).name === 'string'
103
+ typeof (/** @type {Record<string, unknown>} */ (decl.id).name) === 'string'
104
104
  ) {
105
105
  out.push(/** @type {string} */ (/** @type {Record<string, unknown>} */ (decl.id).name))
106
106
  }
@@ -128,7 +128,11 @@ function hasDefaultExport(program) {
128
128
  const body = /** @type {Record<string, unknown>} */ (program).body
129
129
  if (!Array.isArray(body)) return false
130
130
  for (const node of body) {
131
- if (node && typeof node === 'object' && /** @type {Record<string, unknown>} */ (node).type === 'ExportDefaultDeclaration') {
131
+ if (
132
+ node &&
133
+ typeof node === 'object' &&
134
+ /** @type {Record<string, unknown>} */ (node).type === 'ExportDefaultDeclaration'
135
+ ) {
132
136
  return true
133
137
  }
134
138
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Знаходить імпорти з `ioredis` та `node-redis` (та підшляхів `redis/...`) у джерелах —
3
+ * їх треба замінити на Bun native Redis (`import { redis } from 'bun'`) згідно з
4
+ * `js-bun-redis.mdc` (<https://bun.com/docs/runtime/redis>).
5
+ *
6
+ * Семантика береться з **oxc-parser** (`module.staticImports`) — без regex по тілу файлу.
7
+ * Додатково по AST програми ловимо `require('ioredis')` і динамічний `import('ioredis')`,
8
+ * щоб правило працювало і у CommonJS, і при динамічному `import` у межах одного файлу.
9
+ *
10
+ * `node-redis` публікується під рядом імен:
11
+ * - кореневий пакет `redis` (саме так його імпортують у v4+);
12
+ * - історичний `node-redis` (рідше);
13
+ * - підпакети, які тягнуться разом: `@redis/client`, `@redis/json`, `@redis/search`,
14
+ * `@redis/time-series`, `@redis/bloom` — їх теж треба прибирати разом із основним
15
+ * клієнтом, щоб не лишилось «половини» інтеграції після переходу на Bun.
16
+ *
17
+ * Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках повертається порожній
18
+ * результат — спочатку треба полагодити синтаксис, потім перезапустити перевірку.
19
+ */
20
+ import { parseSync } from 'oxc-parser'
21
+
22
+ import { langFromPath, normalizeSnippet, offsetToLine } from './ast-scan-utils.mjs'
23
+
24
+ const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
25
+ const FORBIDDEN_MODULE_NAMES = new Set([
26
+ 'ioredis',
27
+ 'node-redis',
28
+ 'redis',
29
+ '@redis/client',
30
+ '@redis/json',
31
+ '@redis/search',
32
+ '@redis/time-series',
33
+ '@redis/bloom'
34
+ ])
35
+
36
+ /**
37
+ * Чи є рядок-специфікатор імпорту забороненим (`ioredis`, `node-redis`, `redis`, `redis/...`,
38
+ * `ioredis/...`, `@redis/<sub>`).
39
+ *
40
+ * Використовуємо префікс-збіг для `ioredis/` та `redis/` — щоб ловити підшляхи
41
+ * (`ioredis/built/utils`, `redis/dist/...`), але не зачепити сторонні пакети
42
+ * на кшталт `redis-mock`, які треба валідувати окремо.
43
+ * @param {string} mod рядкове значення з `import '...'` / `require('...')`
44
+ * @returns {boolean} true, якщо такий specifier треба викинути на користь Bun native Redis
45
+ */
46
+ function isForbiddenRedisModule(mod) {
47
+ if (FORBIDDEN_MODULE_NAMES.has(mod)) return true
48
+ return mod.startsWith('ioredis/') || mod.startsWith('redis/') || mod.startsWith('@redis/')
49
+ }
50
+
51
+ /**
52
+ * Перевіряє, чи це виклик `require('<module>')` з рядковим аргументом.
53
+ * @param {Record<string, unknown> | null | undefined} node вузол AST
54
+ * @returns {string | null} ім'я модуля з аргументу, інакше `null`
55
+ */
56
+ function requireCallModule(node) {
57
+ if (!node || node.type !== 'CallExpression') return null
58
+ const callee = node.callee
59
+ if (!callee || callee.type !== 'Identifier' || callee.name !== 'require') return null
60
+ const arg = node.arguments?.[0]
61
+ if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') return null
62
+ return arg.value
63
+ }
64
+
65
+ /**
66
+ * Перевіряє, чи це динамічний `import('<module>')` з рядковим аргументом.
67
+ * @param {Record<string, unknown> | null | undefined} node вузол AST
68
+ * @returns {string | null} ім'я модуля, інакше `null`
69
+ */
70
+ function dynamicImportModule(node) {
71
+ if (!node || node.type !== 'ImportExpression') return null
72
+ const src = node.source
73
+ if (!src || src.type !== 'Literal' || typeof src.value !== 'string') return null
74
+ return src.value
75
+ }
76
+
77
+ /**
78
+ * Простий рекурсивний обхід AST: заходимо в усі обʼєкти/масиви, щоб знайти require/import-вузли.
79
+ * @param {unknown} node корінь або під-вузол AST
80
+ * @param {(n: unknown) => void} visit виклик для кожного обʼєкта-вузла
81
+ * @returns {void}
82
+ */
83
+ function walkAst(node, visit) {
84
+ if (!node || typeof node !== 'object') return
85
+ if (Array.isArray(node)) {
86
+ for (const item of node) walkAst(item, visit)
87
+ return
88
+ }
89
+ if (typeof node.type === 'string') {
90
+ visit(node)
91
+ }
92
+ for (const key of Object.keys(node)) {
93
+ if (key !== 'parent') {
94
+ const v = node[key]
95
+ if (v && typeof v === 'object') walkAst(v, visit)
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Знаходить заборонені імпорти/require з `ioredis` / `node-redis` у тексті.
102
+ * @param {string} content вихідний код
103
+ * @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/foo.ts`)
104
+ * @returns {{ line: number, snippet: string, module: string }[]} список порушень
105
+ */
106
+ export function findRedisImportsInText(content, virtualPath = 'scan.ts') {
107
+ const pathForLang = virtualPath || 'scan.ts'
108
+ const lang = langFromPath(pathForLang)
109
+ let result
110
+ try {
111
+ result = parseSync(pathForLang, content, { lang, sourceType: 'module' })
112
+ } catch {
113
+ return []
114
+ }
115
+ if (result.errors?.length) {
116
+ return []
117
+ }
118
+
119
+ /** @type {{ line: number, snippet: string, module: string }[]} */
120
+ const out = []
121
+
122
+ for (const imp of result.module?.staticImports ?? []) {
123
+ const mod = imp.moduleRequest?.value
124
+ if (typeof mod === 'string' && isForbiddenRedisModule(mod)) {
125
+ out.push({
126
+ line: offsetToLine(content, imp.start),
127
+ snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
128
+ module: mod
129
+ })
130
+ }
131
+ }
132
+
133
+ walkAst(result.program, node => {
134
+ const reqMod = requireCallModule(node)
135
+ if (reqMod && isForbiddenRedisModule(reqMod)) {
136
+ out.push({
137
+ line: offsetToLine(content, node.start),
138
+ snippet: normalizeSnippet(content.slice(node.start, node.end)),
139
+ module: reqMod
140
+ })
141
+ return
142
+ }
143
+ const dynMod = dynamicImportModule(node)
144
+ if (dynMod && isForbiddenRedisModule(dynMod)) {
145
+ out.push({
146
+ line: offsetToLine(content, node.start),
147
+ snippet: normalizeSnippet(content.slice(node.start, node.end)),
148
+ module: dynMod
149
+ })
150
+ }
151
+ })
152
+
153
+ return out
154
+ }
155
+
156
+ /**
157
+ * Чи сканувати цей файл за розширенням (JS/TS-сімʼя).
158
+ * @param {string} relativePath відносний шлях до файлу
159
+ * @returns {boolean} `true`, якщо розширення підходить для пошуку імпорту
160
+ */
161
+ export function isRedisScanSourceFile(relativePath) {
162
+ return SOURCE_FILE_RE.test(relativePath)
163
+ }
164
+
165
+ /**
166
+ * Чи слід пропустити файл під час обходу пакета (декларації типів — лише типи, не виконувані).
167
+ * @param {string} relativePosix шлях з posix-слешами
168
+ * @returns {boolean} `true`, якщо файл не сканувати
169
+ */
170
+ export function shouldSkipFileForRedisScan(relativePosix) {
171
+ return relativePosix.endsWith('.d.ts')
172
+ }
@@ -13,7 +13,7 @@ description: >-
13
13
 
14
14
  ## Передумови
15
15
 
16
- - Чисте робоче дерево (`git status` без незакоммічених змін у `package.json` / `bun.lock` / `node_modules`) — інакше різницю не відрізнити від оновлення.
16
+ - Чисте робоче дерево (`git status` без незакомічених змін у `package.json` / `bun.lock` / `node_modules`) — інакше різницю не відрізнити від оновлення.
17
17
  - Встановлений `bun` і доступний `bunx`.
18
18
  - Запуск з кореня проекту (де лежить `package.json` / `bun.lock`).
19
19
 
@@ -66,6 +66,7 @@ rg -n "<імпорт|функція|опція>" --type ts --type js --type vue
66
66
  ```
67
67
 
68
68
  Класифікувати:
69
+
69
70
  - **сумісно** — проект не використовує зачеплене API → нічого не робити.
70
71
  - **несумісно** — використання знайдено → перейти до п. 6.
71
72