@open-dy/vibecoding-sdk-taro 0.0.1-beta.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.
Files changed (239) hide show
  1. package/dist/adapters/interest-card/eslint/eslint-plugin-custom-navigation.mjs +290 -0
  2. package/dist/adapters/interest-card/eslint/taro.mjs +55 -0
  3. package/dist/adapters/interest-card/runtime/microapp.d.ts +7 -0
  4. package/dist/adapters/interest-card/runtime/microapp.js +8 -0
  5. package/dist/adapters/interest-card/runtime/microapp.js.map +1 -0
  6. package/dist/adapters/interest-card/types/taro.d.ts +28 -0
  7. package/dist/build/config-overrides.d.ts +19 -0
  8. package/dist/build/config-overrides.js +35 -0
  9. package/dist/build/config-overrides.js.map +1 -0
  10. package/dist/build/index.d.ts +1 -0
  11. package/dist/build/index.js +2 -0
  12. package/dist/build/index.js.map +1 -0
  13. package/dist/build/plugins/app-config.cjs +43 -0
  14. package/dist/build/plugins/vite-depend-card.d.ts +29 -0
  15. package/dist/build/plugins/vite-depend-card.js +250 -0
  16. package/dist/build/plugins/vite-depend-card.js.map +1 -0
  17. package/dist/build/plugins/vite-image-asset-import.d.ts +10 -0
  18. package/dist/build/plugins/vite-image-asset-import.js +150 -0
  19. package/dist/build/plugins/vite-image-asset-import.js.map +1 -0
  20. package/dist/build/plugins/vite-inline-json.d.ts +4 -0
  21. package/dist/build/plugins/vite-inline-json.js +129 -0
  22. package/dist/build/plugins/vite-inline-json.js.map +1 -0
  23. package/dist/build/plugins/vite-taro-source-location.d.ts +13 -0
  24. package/dist/build/plugins/vite-taro-source-location.js +159 -0
  25. package/dist/build/plugins/vite-taro-source-location.js.map +1 -0
  26. package/dist/build/taro.d.ts +53 -0
  27. package/dist/build/taro.js +729 -0
  28. package/dist/build/taro.js.map +1 -0
  29. package/dist/components/miniapp-open-card.d.ts +19 -0
  30. package/dist/components/miniapp-open-card.js +3 -0
  31. package/dist/components/miniapp-open-card.js.map +1 -0
  32. package/dist/eslint/eslint-plugin-microapp-pages.mjs +659 -0
  33. package/dist/eslint/server.mjs +69 -0
  34. package/dist/eslint/taro.mjs +553 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/internal/runtime/douyin-open-button.d.ts +29 -0
  39. package/dist/internal/runtime/iframe-auth/identity.d.ts +20 -0
  40. package/dist/internal/runtime/iframe-auth/identity.js +156 -0
  41. package/dist/internal/runtime/iframe-auth/identity.js.map +1 -0
  42. package/dist/internal/runtime/iframe-auth/index.d.ts +2 -0
  43. package/dist/internal/runtime/iframe-auth/index.js +3 -0
  44. package/dist/internal/runtime/iframe-auth/index.js.map +1 -0
  45. package/dist/internal/runtime/iframe-auth/sync.d.ts +2 -0
  46. package/dist/internal/runtime/iframe-auth/sync.js +23 -0
  47. package/dist/internal/runtime/iframe-auth/sync.js.map +1 -0
  48. package/dist/lib/measure.d.ts +21 -0
  49. package/dist/lib/measure.js +101 -0
  50. package/dist/lib/measure.js.map +1 -0
  51. package/dist/lib/platform.d.ts +2 -0
  52. package/dist/lib/platform.js +11 -0
  53. package/dist/lib/platform.js.map +1 -0
  54. package/dist/lib/utils.d.ts +2 -0
  55. package/dist/lib/utils.js +6 -0
  56. package/dist/lib/utils.js.map +1 -0
  57. package/dist/reference/llms.txt +36 -0
  58. package/dist/reference/sdk-api/build/taro/BuildContext.md +53 -0
  59. package/dist/reference/sdk-api/build/taro/CreateMicroappTaroConfigOptions.md +62 -0
  60. package/dist/reference/sdk-api/build/taro/createMicroappTaroConfig.md +66 -0
  61. package/dist/reference/sdk-api/build/taro/index.md +11 -0
  62. package/dist/reference/sdk-api/runtime/CreateMicroappClientOptions.md +47 -0
  63. package/dist/reference/sdk-api/runtime/createMicroappClient.md +378 -0
  64. package/dist/reference/sdk-api/runtime/index.md +10 -0
  65. package/dist/reference/sdk-api.json +1534 -0
  66. package/dist/reference/sdk-api.md +43 -0
  67. package/dist/runtime/client.d.ts +63 -0
  68. package/dist/runtime/client.js +194 -0
  69. package/dist/runtime/client.js.map +1 -0
  70. package/dist/runtime/closed-domain-request-header.d.ts +4 -0
  71. package/dist/runtime/closed-domain-request-header.js +8 -0
  72. package/dist/runtime/closed-domain-request-header.js.map +1 -0
  73. package/dist/runtime/default-client.d.ts +57 -0
  74. package/dist/runtime/default-client.js +21 -0
  75. package/dist/runtime/default-client.js.map +1 -0
  76. package/dist/runtime/default-iframe-auth.d.ts +8 -0
  77. package/dist/runtime/default-iframe-auth.js +10 -0
  78. package/dist/runtime/default-iframe-auth.js.map +1 -0
  79. package/dist/runtime/douyin-container-request.d.ts +11 -0
  80. package/dist/runtime/douyin-container-request.js +301 -0
  81. package/dist/runtime/douyin-container-request.js.map +1 -0
  82. package/dist/runtime/douyin-login-request.d.ts +32 -0
  83. package/dist/runtime/douyin-login-request.js +400 -0
  84. package/dist/runtime/douyin-login-request.js.map +1 -0
  85. package/dist/runtime/globals.d.ts +342 -0
  86. package/dist/runtime/index.d.ts +4 -0
  87. package/dist/runtime/index.js +4 -0
  88. package/dist/runtime/index.js.map +1 -0
  89. package/dist/runtime/network-url.d.ts +3 -0
  90. package/dist/runtime/network-url.js +53 -0
  91. package/dist/runtime/network-url.js.map +1 -0
  92. package/dist/types/taro.d.ts +1 -0
  93. package/dist/ui/accordion.d.ts +17 -0
  94. package/dist/ui/accordion.js +60 -0
  95. package/dist/ui/accordion.js.map +1 -0
  96. package/dist/ui/alert-dialog.d.ts +33 -0
  97. package/dist/ui/alert-dialog.js +96 -0
  98. package/dist/ui/alert-dialog.js.map +1 -0
  99. package/dist/ui/alert.d.ts +11 -0
  100. package/dist/ui/alert.js +49 -0
  101. package/dist/ui/alert.js.map +1 -0
  102. package/dist/ui/aspect-ratio.d.ts +5 -0
  103. package/dist/ui/aspect-ratio.js +10 -0
  104. package/dist/ui/aspect-ratio.js.map +1 -0
  105. package/dist/ui/avatar.d.ts +14 -0
  106. package/dist/ui/avatar.js +50 -0
  107. package/dist/ui/avatar.js.map +1 -0
  108. package/dist/ui/badge.d.ts +8 -0
  109. package/dist/ui/badge.js +27 -0
  110. package/dist/ui/badge.js.map +1 -0
  111. package/dist/ui/button-group.d.ts +14 -0
  112. package/dist/ui/button-group.js +52 -0
  113. package/dist/ui/button-group.js.map +1 -0
  114. package/dist/ui/button.d.ts +30 -0
  115. package/dist/ui/button.js +75 -0
  116. package/dist/ui/button.js.map +1 -0
  117. package/dist/ui/calendar.d.ts +29 -0
  118. package/dist/ui/calendar.js +141 -0
  119. package/dist/ui/calendar.js.map +1 -0
  120. package/dist/ui/card.d.ts +12 -0
  121. package/dist/ui/card.js +38 -0
  122. package/dist/ui/card.js.map +1 -0
  123. package/dist/ui/carousel.d.ts +30 -0
  124. package/dist/ui/carousel.js +60 -0
  125. package/dist/ui/carousel.js.map +1 -0
  126. package/dist/ui/checkbox.d.ts +9 -0
  127. package/dist/ui/checkbox.js +20 -0
  128. package/dist/ui/checkbox.js.map +1 -0
  129. package/dist/ui/collapsible.d.ts +18 -0
  130. package/dist/ui/collapsible.js +53 -0
  131. package/dist/ui/collapsible.js.map +1 -0
  132. package/dist/ui/combobox.d.ts +55 -0
  133. package/dist/ui/combobox.js +165 -0
  134. package/dist/ui/combobox.js.map +1 -0
  135. package/dist/ui/dialog.d.ts +26 -0
  136. package/dist/ui/dialog.js +54 -0
  137. package/dist/ui/dialog.js.map +1 -0
  138. package/dist/ui/drawer.d.ts +21 -0
  139. package/dist/ui/drawer.js +57 -0
  140. package/dist/ui/drawer.js.map +1 -0
  141. package/dist/ui/dropdown-menu.d.ts +52 -0
  142. package/dist/ui/dropdown-menu.js +112 -0
  143. package/dist/ui/dropdown-menu.js.map +1 -0
  144. package/dist/ui/empty.d.ts +12 -0
  145. package/dist/ui/empty.js +35 -0
  146. package/dist/ui/empty.js.map +1 -0
  147. package/dist/ui/field.d.ts +26 -0
  148. package/dist/ui/field.js +117 -0
  149. package/dist/ui/field.js.map +1 -0
  150. package/dist/ui/helpers.d.ts +6 -0
  151. package/dist/ui/helpers.js +23 -0
  152. package/dist/ui/helpers.js.map +1 -0
  153. package/dist/ui/index.d.ts +47 -0
  154. package/dist/ui/index.js +48 -0
  155. package/dist/ui/index.js.map +1 -0
  156. package/dist/ui/input-group.d.ts +17 -0
  157. package/dist/ui/input-group.js +122 -0
  158. package/dist/ui/input-group.js.map +1 -0
  159. package/dist/ui/input-otp.d.ts +18 -0
  160. package/dist/ui/input-otp.js +48 -0
  161. package/dist/ui/input-otp.js.map +1 -0
  162. package/dist/ui/input.d.ts +5 -0
  163. package/dist/ui/input.js +8 -0
  164. package/dist/ui/input.js.map +1 -0
  165. package/dist/ui/item.d.ts +22 -0
  166. package/dist/ui/item.js +83 -0
  167. package/dist/ui/item.js.map +1 -0
  168. package/dist/ui/label.d.ts +3 -0
  169. package/dist/ui/label.js +8 -0
  170. package/dist/ui/label.js.map +1 -0
  171. package/dist/ui/menubar.d.ts +65 -0
  172. package/dist/ui/menubar.js +244 -0
  173. package/dist/ui/menubar.js.map +1 -0
  174. package/dist/ui/navigation-menu.d.ts +27 -0
  175. package/dist/ui/navigation-menu.js +72 -0
  176. package/dist/ui/navigation-menu.js.map +1 -0
  177. package/dist/ui/pagination.d.ts +17 -0
  178. package/dist/ui/pagination.js +28 -0
  179. package/dist/ui/pagination.js.map +1 -0
  180. package/dist/ui/popover.d.ts +27 -0
  181. package/dist/ui/popover.js +139 -0
  182. package/dist/ui/popover.js.map +1 -0
  183. package/dist/ui/portal.d.ts +5 -0
  184. package/dist/ui/portal.js +18 -0
  185. package/dist/ui/portal.js.map +1 -0
  186. package/dist/ui/progress.d.ts +45 -0
  187. package/dist/ui/progress.js +67 -0
  188. package/dist/ui/progress.js.map +1 -0
  189. package/dist/ui/radio-group.d.ts +12 -0
  190. package/dist/ui/radio-group.js +29 -0
  191. package/dist/ui/radio-group.js.map +1 -0
  192. package/dist/ui/resizable.d.ts +20 -0
  193. package/dist/ui/resizable.js +244 -0
  194. package/dist/ui/resizable.js.map +1 -0
  195. package/dist/ui/scroll-area.d.ts +8 -0
  196. package/dist/ui/scroll-area.js +34 -0
  197. package/dist/ui/scroll-area.js.map +1 -0
  198. package/dist/ui/select.d.ts +35 -0
  199. package/dist/ui/select.js +136 -0
  200. package/dist/ui/select.js.map +1 -0
  201. package/dist/ui/separator.d.ts +5 -0
  202. package/dist/ui/separator.js +8 -0
  203. package/dist/ui/separator.js.map +1 -0
  204. package/dist/ui/sheet.d.ts +26 -0
  205. package/dist/ui/sheet.js +67 -0
  206. package/dist/ui/sheet.js.map +1 -0
  207. package/dist/ui/skeleton.d.ts +3 -0
  208. package/dist/ui/skeleton.js +8 -0
  209. package/dist/ui/skeleton.js.map +1 -0
  210. package/dist/ui/slider.d.ts +12 -0
  211. package/dist/ui/slider.js +94 -0
  212. package/dist/ui/slider.js.map +1 -0
  213. package/dist/ui/sonner.d.ts +6 -0
  214. package/dist/ui/sonner.js +13 -0
  215. package/dist/ui/sonner.js.map +1 -0
  216. package/dist/ui/spinner.d.ts +3 -0
  217. package/dist/ui/spinner.js +9 -0
  218. package/dist/ui/spinner.js.map +1 -0
  219. package/dist/ui/switch.d.ts +11 -0
  220. package/dist/ui/switch.js +33 -0
  221. package/dist/ui/switch.js.map +1 -0
  222. package/dist/ui/table.d.ts +13 -0
  223. package/dist/ui/table.js +54 -0
  224. package/dist/ui/table.js.map +1 -0
  225. package/dist/ui/tabs.d.ts +23 -0
  226. package/dist/ui/tabs.js +61 -0
  227. package/dist/ui/tabs.js.map +1 -0
  228. package/dist/ui/tailwind.css +2 -0
  229. package/dist/ui/textarea.d.ts +3 -0
  230. package/dist/ui/textarea.js +13 -0
  231. package/dist/ui/textarea.js.map +1 -0
  232. package/dist/ui/theme.css +234 -0
  233. package/dist/ui/toggle-group.d.ts +19 -0
  234. package/dist/ui/toggle-group.js +73 -0
  235. package/dist/ui/toggle-group.js.map +1 -0
  236. package/dist/ui/toggle.d.ts +14 -0
  237. package/dist/ui/toggle.js +41 -0
  238. package/dist/ui/toggle.js.map +1 -0
  239. package/package.json +97 -0
@@ -0,0 +1,659 @@
1
+ /**
2
+ * 校验小程序页面路由与 app.config.ts 中 pages 一致,避免「写了跳转 / 建了页面但未注册」。
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { createRequire } from 'node:module';
7
+
8
+ /** TypeScript 为 CJS 包,在 ESLint 的 ESM 插件里用 require 才能拿到完整的 ts 命名空间 API */
9
+ const ts = createRequire(import.meta.url)('typescript');
10
+
11
+ const NAV_METHODS = new Set([
12
+ 'navigateTo',
13
+ 'redirectTo',
14
+ 'reLaunch',
15
+ 'switchTab',
16
+ ]);
17
+ const PAGE_SOURCE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
18
+
19
+ /** @param {string} route */
20
+ function normalizeRoute(route) {
21
+ if (!route || typeof route !== 'string') return '';
22
+ const noQuery = route.split('?')[0];
23
+ return noQuery.replace(/^\/+/, '').replace(/\/+$/, '');
24
+ }
25
+
26
+ function splitTailwindClassToken(token) {
27
+ let bracketDepth = 0;
28
+ let lastSeparatorIndex = -1;
29
+
30
+ for (let index = 0; index < token.length; index += 1) {
31
+ const char = token[index];
32
+
33
+ if (char === '[') bracketDepth += 1;
34
+ if (char === ']') bracketDepth -= 1;
35
+ if (char === ':' && bracketDepth === 0) lastSeparatorIndex = index;
36
+ }
37
+
38
+ return lastSeparatorIndex >= 0
39
+ ? token.slice(lastSeparatorIndex + 1)
40
+ : token;
41
+ }
42
+
43
+ function collectStaticStrings(node, result = []) {
44
+ if (!node) return result;
45
+
46
+ if (node.type === 'Literal' && typeof node.value === 'string') {
47
+ result.push(node.value);
48
+ return result;
49
+ }
50
+
51
+ if (node.type === 'TemplateLiteral') {
52
+ for (const quasi of node.quasis) {
53
+ if (quasi.value.cooked) result.push(quasi.value.cooked);
54
+ }
55
+
56
+ for (const expression of node.expressions) {
57
+ collectStaticStrings(expression, result);
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ for (const key of [
64
+ 'left',
65
+ 'right',
66
+ 'argument',
67
+ 'callee',
68
+ 'test',
69
+ 'consequent',
70
+ 'alternate',
71
+ 'object',
72
+ 'property',
73
+ ]) {
74
+ collectStaticStrings(node[key], result);
75
+ }
76
+
77
+ for (const key of ['arguments', 'elements', 'expressions']) {
78
+ if (Array.isArray(node[key])) {
79
+ for (const child of node[key]) collectStaticStrings(child, result);
80
+ }
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ function routeFromPageFilename(filePath) {
87
+ const normalized = filePath.replace(/\\/g, '/');
88
+ const match = normalized.match(/\/src\/pages\/(.+)\/index\.[jt]sx?$/);
89
+ if (!match) return '';
90
+
91
+ return normalizeRoute(`pages/${match[1]}/index`);
92
+ }
93
+
94
+ function getPageFileCandidates(cwd, route) {
95
+ const normalized = normalizeRoute(route);
96
+ return PAGE_SOURCE_EXTENSIONS.map((extension) =>
97
+ path.join(cwd, 'src', `${normalized}${extension}`));
98
+ }
99
+
100
+ function formatRelativeCandidates(cwd, candidates) {
101
+ return candidates.map((candidate) => path.relative(cwd, candidate));
102
+ }
103
+
104
+ function buildMissingPageSuggestion(route) {
105
+ const normalized = normalizeRoute(route);
106
+ const indexRoute = normalized.endsWith('/index') ? normalized : `${normalized}/index`;
107
+ const indexSource = `src/${indexRoute}.tsx`;
108
+ const directSource = `src/${normalized}.tsx`;
109
+
110
+ if (normalized.endsWith('/index')) {
111
+ return (
112
+ `如果实际页面文件是 ${indexSource},请保持 pages 中声明为 '${indexRoute}';` +
113
+ ` 否则请创建 ${directSource} / .ts / .jsx / .js 中的一个。`
114
+ );
115
+ }
116
+
117
+ return (
118
+ `如果实际页面文件是 ${indexSource},请把 pages 中的 '${normalized}' 改成 '${indexRoute}';` +
119
+ ` 如果你本来想声明单文件页面,请创建 ${directSource} / .ts / .jsx / .js 中的一个。`
120
+ );
121
+ }
122
+
123
+ function isDirectReturnedJsxElementAttribute(node) {
124
+ const element = node.parent?.parent;
125
+ if (element?.type !== 'JSXElement') return false;
126
+
127
+ let parent = element.parent;
128
+ while (
129
+ parent &&
130
+ [
131
+ 'ParenthesizedExpression',
132
+ 'ChainExpression',
133
+ 'TSAsExpression',
134
+ 'TSTypeAssertion',
135
+ 'TSNonNullExpression',
136
+ 'ConditionalExpression',
137
+ 'LogicalExpression',
138
+ ].includes(parent.type)
139
+ ) {
140
+ parent = parent.parent;
141
+ }
142
+
143
+ return parent?.type === 'ReturnStatement' || parent?.type === 'ArrowFunctionExpression';
144
+ }
145
+
146
+ /**
147
+ * @param {string} pagesRoot absolute path to src/pages
148
+ * @returns {string[]}
149
+ */
150
+ function collectPageRoutesFromDisk(pagesRoot) {
151
+ const routes = [];
152
+ if (!fs.existsSync(pagesRoot)) return routes;
153
+
154
+ function walk(dir, parts) {
155
+ for (const name of fs.readdirSync(dir)) {
156
+ const full = path.join(dir, name);
157
+ if (!fs.statSync(full).isDirectory()) continue;
158
+ const nextParts = [...parts, name];
159
+ const indexTsx = path.join(full, 'index.tsx');
160
+ const indexTs = path.join(full, 'index.ts');
161
+ if (fs.existsSync(indexTsx) || fs.existsSync(indexTs)) {
162
+ routes.push(`pages/${nextParts.join('/')}/index`);
163
+ }
164
+ walk(full, nextParts);
165
+ }
166
+ }
167
+
168
+ walk(pagesRoot, []);
169
+ return routes;
170
+ }
171
+
172
+ /**
173
+ * @param {string} filePath
174
+ * @param {string} text
175
+ * @returns {string[] | null}
176
+ */
177
+ function extractPagesFromAppConfigSource(filePath, text) {
178
+ const sf = ts.createSourceFile(
179
+ filePath,
180
+ text,
181
+ ts.ScriptTarget.Latest,
182
+ true,
183
+ ts.ScriptKind.TS
184
+ );
185
+
186
+ /** @type {string[] | null} */
187
+ let pages = null;
188
+
189
+ function fromObjectLiteral(obj) {
190
+ let foundPagesKey = false;
191
+ const out = [];
192
+ for (const prop of obj.properties) {
193
+ if (!ts.isPropertyAssignment(prop)) continue;
194
+ const key =
195
+ ts.isIdentifier(prop.name) ? prop.name.text
196
+ : ts.isStringLiteral(prop.name) ? prop.name.text
197
+ : null;
198
+ if (key !== 'pages' || !ts.isArrayLiteralExpression(prop.initializer)) continue;
199
+ foundPagesKey = true;
200
+ for (const el of prop.initializer.elements) {
201
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
202
+ out.push(el.text);
203
+ }
204
+ }
205
+ }
206
+ return foundPagesKey ? out : null;
207
+ }
208
+
209
+ function visit(node) {
210
+ if (pages !== null) return;
211
+
212
+ // TS 5.x 将「export default expr」解析为 ExportAssignment(isExportEquals === false),
213
+ // 不再使用 ExportDefaultDeclaration / isExportDefaultDeclaration。
214
+ if (ts.isExportAssignment(node) && !node.isExportEquals && node.expression) {
215
+ const expr = node.expression;
216
+ if (ts.isCallExpression(expr) && expr.arguments[0]) {
217
+ const arg0 = expr.arguments[0];
218
+ if (ts.isObjectLiteralExpression(arg0)) {
219
+ pages = fromObjectLiteral(arg0);
220
+ }
221
+ } else if (ts.isObjectLiteralExpression(expr)) {
222
+ pages = fromObjectLiteral(expr);
223
+ }
224
+ }
225
+
226
+ ts.forEachChild(node, visit);
227
+ }
228
+
229
+ visit(sf);
230
+ return pages;
231
+ }
232
+
233
+ /**
234
+ * @param {string} filePath
235
+ * @param {string} text
236
+ * @returns {{ hasTabBar: boolean; pagePaths: string[] } | null}
237
+ */
238
+ function extractTabBarFromAppConfigSource(filePath, text) {
239
+ const sf = ts.createSourceFile(
240
+ filePath,
241
+ text,
242
+ ts.ScriptTarget.Latest,
243
+ true,
244
+ ts.ScriptKind.TS
245
+ );
246
+
247
+ /** @type {{ hasTabBar: boolean; pagePaths: string[] } | null} */
248
+ let tabBar = null;
249
+
250
+ function getPropertyName(prop) {
251
+ return ts.isIdentifier(prop.name) ? prop.name.text
252
+ : ts.isStringLiteral(prop.name) ? prop.name.text
253
+ : null;
254
+ }
255
+
256
+ function getStringProperty(obj, propertyName) {
257
+ for (const prop of obj.properties) {
258
+ if (!ts.isPropertyAssignment(prop) || getPropertyName(prop) !== propertyName) continue;
259
+ if (ts.isStringLiteral(prop.initializer) || ts.isNoSubstitutionTemplateLiteral(prop.initializer)) {
260
+ return prop.initializer.text;
261
+ }
262
+ }
263
+
264
+ return null;
265
+ }
266
+
267
+ function fromObjectLiteral(obj) {
268
+ let foundTabBar = false;
269
+ const pagePaths = [];
270
+
271
+ for (const prop of obj.properties) {
272
+ if (!ts.isPropertyAssignment(prop) || getPropertyName(prop) !== 'tabBar') continue;
273
+ foundTabBar = true;
274
+ if (!ts.isObjectLiteralExpression(prop.initializer)) continue;
275
+
276
+ for (const tabBarProp of prop.initializer.properties) {
277
+ if (!ts.isPropertyAssignment(tabBarProp) || getPropertyName(tabBarProp) !== 'list') continue;
278
+ if (!ts.isArrayLiteralExpression(tabBarProp.initializer)) continue;
279
+
280
+ for (const item of tabBarProp.initializer.elements) {
281
+ if (!ts.isObjectLiteralExpression(item)) continue;
282
+ const pagePath = getStringProperty(item, 'pagePath');
283
+ if (pagePath) pagePaths.push(pagePath);
284
+ }
285
+ }
286
+ }
287
+
288
+ return foundTabBar ? { hasTabBar: true, pagePaths } : { hasTabBar: false, pagePaths: [] };
289
+ }
290
+
291
+ function visit(node) {
292
+ if (tabBar !== null) return;
293
+
294
+ if (ts.isExportAssignment(node) && !node.isExportEquals && node.expression) {
295
+ const expr = node.expression;
296
+ if (ts.isCallExpression(expr) && expr.arguments[0]) {
297
+ const arg0 = expr.arguments[0];
298
+ if (ts.isObjectLiteralExpression(arg0)) {
299
+ tabBar = fromObjectLiteral(arg0);
300
+ }
301
+ } else if (ts.isObjectLiteralExpression(expr)) {
302
+ tabBar = fromObjectLiteral(expr);
303
+ }
304
+ }
305
+
306
+ ts.forEachChild(node, visit);
307
+ }
308
+
309
+ visit(sf);
310
+ return tabBar;
311
+ }
312
+
313
+ /** @type {Map<string, { pages: string[]; mtime: number } | { error: string }>} */
314
+ const pagesCache = new Map();
315
+
316
+ /** @type {Map<string, { hasTabBar: boolean; pagePaths: string[]; mtime: number } | { error: string }>} */
317
+ const tabBarCache = new Map();
318
+
319
+ /** 解析 app.config 失败时,每个 cwd 只提示一次,避免每个 navigateTo 都报错 */
320
+ const appConfigParseErrorLogged = new Set();
321
+
322
+ function loadRegisteredPages(cwd) {
323
+ const appConfigPath = path.join(cwd, 'src/app.config.ts');
324
+ if (!fs.existsSync(appConfigPath)) {
325
+ return { pages: [], skip: true };
326
+ }
327
+
328
+ try {
329
+ const st = fs.statSync(appConfigPath);
330
+ const cacheKey = appConfigPath;
331
+ const cached = pagesCache.get(cacheKey);
332
+ if (cached && !('error' in cached) && cached.mtime === st.mtimeMs) {
333
+ return { pages: cached.pages, skip: false };
334
+ }
335
+
336
+ const text = fs.readFileSync(appConfigPath, 'utf8');
337
+ const parsed = extractPagesFromAppConfigSource(appConfigPath, text);
338
+ if (!parsed) {
339
+ pagesCache.set(cacheKey, { error: '无法解析 pages 数组' });
340
+ return { pages: [], skip: true, parseError: true };
341
+ }
342
+
343
+ const normalized = parsed.map(p => normalizeRoute(p));
344
+ pagesCache.set(cacheKey, { pages: normalized, mtime: st.mtimeMs });
345
+ return { pages: normalized, skip: false };
346
+ } catch (e) {
347
+ return { pages: [], skip: true, parseError: true };
348
+ }
349
+ }
350
+
351
+ function loadTabBarPages(cwd) {
352
+ const appConfigPath = path.join(cwd, 'src/app.config.ts');
353
+ if (!fs.existsSync(appConfigPath)) {
354
+ return { hasTabBar: false, pagePaths: [], skip: true };
355
+ }
356
+
357
+ try {
358
+ const st = fs.statSync(appConfigPath);
359
+ const cached = tabBarCache.get(appConfigPath);
360
+ if (cached && !('error' in cached) && cached.mtime === st.mtimeMs) {
361
+ return { hasTabBar: cached.hasTabBar, pagePaths: cached.pagePaths, skip: false };
362
+ }
363
+
364
+ const text = fs.readFileSync(appConfigPath, 'utf8');
365
+ const parsed = extractTabBarFromAppConfigSource(appConfigPath, text);
366
+ if (!parsed) {
367
+ tabBarCache.set(appConfigPath, { error: '无法解析 tabBar 配置' });
368
+ return { hasTabBar: false, pagePaths: [], skip: true, parseError: true };
369
+ }
370
+
371
+ const normalized = parsed.pagePaths.map(p => normalizeRoute(p));
372
+ tabBarCache.set(appConfigPath, {
373
+ hasTabBar: parsed.hasTabBar,
374
+ pagePaths: normalized,
375
+ mtime: st.mtimeMs,
376
+ });
377
+
378
+ return { hasTabBar: parsed.hasTabBar, pagePaths: normalized, skip: false };
379
+ } catch (e) {
380
+ return { hasTabBar: false, pagePaths: [], skip: true, parseError: true };
381
+ }
382
+ }
383
+
384
+ function isStaticUrlString(node) {
385
+ if (!node) return null;
386
+ if (node.type === 'Literal' && typeof node.value === 'string') return node.value;
387
+ if (node.type === 'TemplateLiteral' && node.expressions.length === 0 && node.quasis.length === 1) {
388
+ return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
389
+ }
390
+ return null;
391
+ }
392
+
393
+ function getUrlFromNavigateArg(arg) {
394
+ if (!arg || arg.type !== 'ObjectExpression') return null;
395
+ for (const prop of arg.properties) {
396
+ if (prop.type !== 'Property' || prop.key == null) continue;
397
+ const key =
398
+ prop.key.type === 'Identifier' ? prop.key.name
399
+ : prop.key.type === 'Literal' && typeof prop.key.value === 'string' ? prop.key.value
400
+ : null;
401
+ if (key !== 'url') continue;
402
+ return isStaticUrlString(prop.value);
403
+ }
404
+ return null;
405
+ }
406
+
407
+ /** @type {import('eslint').Rule.RuleModule} */
408
+ const navUrlInAppPages = {
409
+ meta: {
410
+ type: 'problem',
411
+ docs: {
412
+ description:
413
+ 'Taro.navigateTo / redirectTo / reLaunch / switchTab 的静态 url 必须在 src/app.config.ts 的 pages 中注册',
414
+ },
415
+ schema: [],
416
+ },
417
+ create(context) {
418
+ const cwd = context.cwd ?? process.cwd();
419
+ const taroDefaultName = new Map(); // local name -> true
420
+ const taroNavLocals = new Set();
421
+
422
+ return {
423
+ ImportDeclaration(node) {
424
+ if (node.source.value !== '@tarojs/taro') return;
425
+ for (const sp of node.specifiers) {
426
+ if (sp.type === 'ImportDefaultSpecifier') {
427
+ taroDefaultName.set(sp.local.name, true);
428
+ }
429
+ if (sp.type === 'ImportNamespaceSpecifier') {
430
+ taroDefaultName.set(sp.local.name, true);
431
+ }
432
+ if (sp.type === 'ImportSpecifier' && sp.imported && sp.imported.type === 'Identifier') {
433
+ const name = sp.imported.name;
434
+ if (NAV_METHODS.has(name)) {
435
+ taroNavLocals.add(sp.local.name);
436
+ }
437
+ }
438
+ }
439
+ },
440
+
441
+ CallExpression(node) {
442
+ const callee = node.callee;
443
+ let method = null;
444
+
445
+ if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier') {
446
+ const obj = callee.object;
447
+ const prop = callee.property.name;
448
+ if (!NAV_METHODS.has(prop)) return;
449
+ if (obj.type === 'Identifier' && taroDefaultName.has(obj.name)) {
450
+ method = prop;
451
+ }
452
+ } else if (callee.type === 'Identifier' && taroNavLocals.has(callee.name)) {
453
+ method = callee.name;
454
+ }
455
+
456
+ if (!method) return;
457
+
458
+ const arg0 = node.arguments[0];
459
+ if (!arg0 || arg0.type !== 'ObjectExpression') return;
460
+
461
+ const rawUrl = getUrlFromNavigateArg(arg0);
462
+ if (rawUrl == null) return;
463
+
464
+ const { pages, skip, parseError } = loadRegisteredPages(cwd);
465
+ if (skip) {
466
+ if (parseError && !appConfigParseErrorLogged.has(cwd)) {
467
+ appConfigParseErrorLogged.add(cwd);
468
+ context.report({
469
+ node,
470
+ message:
471
+ '无法读取或解析 src/app.config.ts 中的 pages,请确认 defineAppConfig({ pages: [...] }) 写法正确。',
472
+ });
473
+ }
474
+ return;
475
+ }
476
+
477
+ const normalized = normalizeRoute(rawUrl);
478
+ if (!normalized) return;
479
+
480
+ if (!pages.includes(normalized)) {
481
+ context.report({
482
+ node,
483
+ message:
484
+ `路由未在 app.config.ts 的 pages 中注册:「${normalized}」。请把该路径加入 pages 数组后再跳转(当前已注册:${pages.length ? pages.join(', ') : '无'})。`,
485
+ });
486
+ }
487
+ },
488
+ };
489
+ },
490
+ };
491
+
492
+ /** @type {import('eslint').Rule.RuleModule} */
493
+ const pageDirsInAppPages = {
494
+ meta: {
495
+ type: 'problem',
496
+ docs: {
497
+ description: 'src/pages 下每个含 index 的页面目录必须在 app.config.ts 的 pages 中注册',
498
+ },
499
+ schema: [],
500
+ },
501
+ create(context) {
502
+ const filePath = context.filename.replace(/\\/g, '/');
503
+ if (!filePath.endsWith('src/app.config.ts')) {
504
+ return {};
505
+ }
506
+
507
+ return {
508
+ Program(node) {
509
+ const cwd = context.cwd ?? process.cwd();
510
+ const pagesRoot = path.join(cwd, 'src/pages');
511
+ const diskRoutes = collectPageRoutesFromDisk(pagesRoot);
512
+ const sc = context.sourceCode;
513
+ const sourceText = sc.getText(node);
514
+ const declared = extractPagesFromAppConfigSource(filePath, sourceText);
515
+
516
+ if (!declared) {
517
+ context.report({
518
+ node,
519
+ message:
520
+ '无法从本文件解析 pages 数组,请使用 defineAppConfig({ pages: [ \'pages/...\' ] }) 形式声明。',
521
+ });
522
+ return;
523
+ }
524
+
525
+ const declaredSet = new Set(declared.map(p => normalizeRoute(p)));
526
+
527
+ for (const route of diskRoutes) {
528
+ const n = normalizeRoute(route);
529
+ const shorthand = n.replace(/\/index$/, '');
530
+ if (shorthand !== n && declaredSet.has(shorthand)) {
531
+ continue;
532
+ }
533
+ if (!declaredSet.has(n)) {
534
+ context.report({
535
+ node,
536
+ message: `磁盘上存在页面「${n}」,但未在 pages 数组中注册。请添加 '${route}'(或等价路径)。`,
537
+ });
538
+ }
539
+ }
540
+ },
541
+ };
542
+ },
543
+ };
544
+
545
+ /** @type {import('eslint').Rule.RuleModule} */
546
+ const declaredPagesMustExist = {
547
+ meta: {
548
+ type: 'problem',
549
+ docs: {
550
+ description: 'app.config.ts 中声明的 pages 必须映射到真实页面文件',
551
+ },
552
+ schema: [],
553
+ },
554
+ create(context) {
555
+ const filePath = context.filename.replace(/\\/g, '/');
556
+ if (!filePath.endsWith('src/app.config.ts')) {
557
+ return {};
558
+ }
559
+
560
+ return {
561
+ Program(node) {
562
+ const cwd = context.cwd ?? process.cwd();
563
+ const sc = context.sourceCode;
564
+ const sourceText = sc.getText(node);
565
+ const declared = extractPagesFromAppConfigSource(filePath, sourceText);
566
+
567
+ if (!declared) {
568
+ context.report({
569
+ node,
570
+ message:
571
+ '无法从本文件解析 pages 数组,请使用 defineAppConfig({ pages: [ \'pages/...\' ] }) 形式声明。',
572
+ });
573
+ return;
574
+ }
575
+
576
+ for (const route of declared) {
577
+ const normalized = normalizeRoute(route);
578
+ const candidates = getPageFileCandidates(cwd, normalized);
579
+ const exists = candidates.some((candidate) => fs.existsSync(candidate));
580
+
581
+ if (exists) continue;
582
+
583
+ context.report({
584
+ node,
585
+ message:
586
+ `src/app.config.ts 的 pages 配置有误:声明了不存在的页面「${normalized}」。` +
587
+ ` Taro 这里需要填写完整的“路径 + 文件名”。` +
588
+ ` ${buildMissingPageSuggestion(normalized)}` +
589
+ ` 当前尝试匹配:${formatRelativeCandidates(cwd, candidates).join(', ')}。`,
590
+ });
591
+ }
592
+ },
593
+ };
594
+ },
595
+ };
596
+
597
+ /** @type {import('eslint').Rule.RuleModule} */
598
+ const noScreenHeightInTabBarPage = {
599
+ meta: {
600
+ type: 'problem',
601
+ docs: {
602
+ description:
603
+ 'TabBar 页面根容器禁止使用 min-h-screen / h-screen,避免超过 taro-tabbar__panel 已计算的可用高度',
604
+ },
605
+ schema: [],
606
+ },
607
+ create(context) {
608
+ const cwd = context.cwd ?? process.cwd();
609
+ const pageRoute = routeFromPageFilename(context.filename);
610
+ if (!pageRoute) return {};
611
+
612
+ const { hasTabBar, pagePaths, skip } = loadTabBarPages(cwd);
613
+ if (skip || !hasTabBar || !pagePaths.includes(pageRoute)) return {};
614
+
615
+ function reportScreenHeightClass(node, className) {
616
+ const violating = className
617
+ .split(/\s+/)
618
+ .map(token => token.trim())
619
+ .filter(Boolean)
620
+ .filter(token => /^(?:!)?(?:min-h-screen|h-screen)$/.test(splitTailwindClassToken(token)));
621
+
622
+ if (violating.length === 0) return;
623
+
624
+ context.report({
625
+ node,
626
+ message:
627
+ `TabBar 页面高度规范:检测到 [${violating.join(', ')}]。` +
628
+ 'Taro TabBar 页面已由 taro-tabbar__panel 计算可用高度,页面根容器请改用 h-full flex flex-col,内容区使用 flex-1 overflow-auto。',
629
+ });
630
+ }
631
+
632
+ return {
633
+ JSXAttribute(node) {
634
+ if (node.name?.name !== 'className') return;
635
+ if (!isDirectReturnedJsxElementAttribute(node)) return;
636
+
637
+ if (node.value?.type === 'Literal' && typeof node.value.value === 'string') {
638
+ reportScreenHeightClass(node, node.value.value);
639
+ return;
640
+ }
641
+
642
+ if (node.value?.type !== 'JSXExpressionContainer') return;
643
+
644
+ for (const className of collectStaticStrings(node.value.expression)) {
645
+ reportScreenHeightClass(node, className);
646
+ }
647
+ },
648
+ };
649
+ },
650
+ };
651
+
652
+ export default {
653
+ rules: {
654
+ 'nav-url-in-app-pages': navUrlInAppPages,
655
+ 'page-dirs-in-app-pages': pageDirsInAppPages,
656
+ 'declared-pages-must-exist': declaredPagesMustExist,
657
+ 'no-screen-height-in-tabbar-page': noScreenHeightInTabBarPage,
658
+ },
659
+ };