@ai0x0/utils 0.0.26 → 0.0.29

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 (28) hide show
  1. package/README.md +2 -2
  2. package/es/backend/route-operation/delete-operation.d.ts +1 -8
  3. package/es/backend/route-operation/get-list-operation.d.ts +1 -8
  4. package/es/backend/route-operation/get-operation.d.ts +1 -8
  5. package/es/backend/route-operation/post-operation.d.ts +1 -8
  6. package/es/backend/route-operation/post-operation.js +40 -41
  7. package/es/backend/route-operation/put-operation.d.ts +1 -9
  8. package/es/backend/route-operation/put-operation.js +40 -41
  9. package/eslint-rules/index.js +40 -0
  10. package/eslint-rules/no-antd-space.js +63 -0
  11. package/eslint-rules/no-consecutive-setstate.js +87 -0
  12. package/eslint-rules/no-hardcoded-style.js +437 -0
  13. package/eslint-rules/no-one-letter-vars.js +111 -0
  14. package/eslint-rules/no-then.js +40 -0
  15. package/eslint-rules/no-use-request-run.js +54 -0
  16. package/eslint-rules/require-form-convention.js +200 -0
  17. package/eslint-rules/require-pro-components.js +131 -0
  18. package/eslint-rules/require-section-divider.js +116 -0
  19. package/eslint-rules/require-use-form.js +77 -0
  20. package/lib/backend/route-operation/delete-operation.d.ts +1 -8
  21. package/lib/backend/route-operation/get-list-operation.d.ts +1 -8
  22. package/lib/backend/route-operation/get-operation.d.ts +1 -8
  23. package/lib/backend/route-operation/get-operation.js +3 -1
  24. package/lib/backend/route-operation/post-operation.d.ts +1 -8
  25. package/lib/backend/route-operation/post-operation.js +9 -8
  26. package/lib/backend/route-operation/put-operation.d.ts +1 -9
  27. package/lib/backend/route-operation/put-operation.js +9 -14
  28. package/package.json +2 -1
@@ -0,0 +1,437 @@
1
+ /**
2
+ * no-hardcoded-style
3
+ * ------------------
4
+ * 禁止在 JSX style / createStyles / css`` 中直接写死样式值。
5
+ * 强制使用 antd token(theme.useToken() 或 createStyles(({token}) => ...))。
6
+ *
7
+ * 白名单:
8
+ * - token.xxx / token() / calc(...) 表达式
9
+ * - 0 值
10
+ * - CSS 功能性关键字(auto, none, hidden, pointer 等)
11
+ * - 百分比 / 带单位字符串
12
+ * - 条件/逻辑/变量/函数调用(无法静态判断)
13
+ * - css\`...\` 中的 \${token.xxx} 插值
14
+ */
15
+
16
+ // ==============================================================================
17
+ // 白名单
18
+ // ==============================================================================
19
+
20
+ const ALLOWED_LITERALS = new Set([
21
+ "auto",
22
+ "none",
23
+ "hidden",
24
+ "visible",
25
+ "pre-wrap",
26
+ "pre",
27
+ "normal",
28
+ "pointer",
29
+ "default",
30
+ "move",
31
+ "not-allowed",
32
+ "grab",
33
+ "grabbing",
34
+ "center",
35
+ "flex-start",
36
+ "flex-end",
37
+ "space-between",
38
+ "space-around",
39
+ "left",
40
+ "right",
41
+ "top",
42
+ "bottom",
43
+ "start",
44
+ "end",
45
+ "solid",
46
+ "dashed",
47
+ "dotted",
48
+ "double",
49
+ "groove",
50
+ "ridge",
51
+ "inset",
52
+ "outset",
53
+ "block",
54
+ "inline-block",
55
+ "inline",
56
+ "flex",
57
+ "grid",
58
+ "contents",
59
+ "row",
60
+ "column",
61
+ "row-reverse",
62
+ "column-reverse",
63
+ "wrap",
64
+ "nowrap",
65
+ "wrap-reverse",
66
+ "relative",
67
+ "absolute",
68
+ "fixed",
69
+ "sticky",
70
+ "static",
71
+ "bold",
72
+ "bolder",
73
+ "lighter",
74
+ "100",
75
+ "200",
76
+ "300",
77
+ "400",
78
+ "500",
79
+ "600",
80
+ "700",
81
+ "800",
82
+ "900",
83
+ "italic",
84
+ "oblique",
85
+ "uppercase",
86
+ "lowercase",
87
+ "capitalize",
88
+ "underline",
89
+ "overline",
90
+ "line-through",
91
+ "blink",
92
+ "collapse",
93
+ "separate",
94
+ "border-box",
95
+ "content-box",
96
+ "padding-box",
97
+ "contain",
98
+ "cover",
99
+ "fill",
100
+ "scale-down",
101
+ ]);
102
+
103
+ const TOKEN_KEYS = new Set([
104
+ "margin",
105
+ "marginTop",
106
+ "marginBottom",
107
+ "marginLeft",
108
+ "marginRight",
109
+ "marginInline",
110
+ "marginInlineStart",
111
+ "marginInlineEnd",
112
+ "marginBlock",
113
+ "marginBlockStart",
114
+ "marginBlockEnd",
115
+ "padding",
116
+ "paddingTop",
117
+ "paddingBottom",
118
+ "paddingLeft",
119
+ "paddingRight",
120
+ "paddingInline",
121
+ "paddingInlineStart",
122
+ "paddingInlineEnd",
123
+ "paddingBlock",
124
+ "paddingBlockStart",
125
+ "paddingBlockEnd",
126
+ "gap",
127
+ "rowGap",
128
+ "columnGap",
129
+ "color",
130
+ "background",
131
+ "backgroundColor",
132
+ "borderColor",
133
+ "borderTopColor",
134
+ "borderBottomColor",
135
+ "borderLeftColor",
136
+ "borderRightColor",
137
+ "outlineColor",
138
+ "border",
139
+ "borderTop",
140
+ "borderBottom",
141
+ "borderLeft",
142
+ "borderRight",
143
+ "borderWidth",
144
+ "borderTopWidth",
145
+ "borderBottomWidth",
146
+ "borderLeftWidth",
147
+ "borderRightWidth",
148
+ "borderStyle",
149
+ "borderTopStyle",
150
+ "borderBottomStyle",
151
+ "borderLeftStyle",
152
+ "borderRightStyle",
153
+ "borderRadius",
154
+ "borderTopLeftRadius",
155
+ "borderTopRightRadius",
156
+ "borderBottomLeftRadius",
157
+ "borderBottomRightRadius",
158
+ "fontSize",
159
+ "fontWeight",
160
+ "lineHeight",
161
+ "letterSpacing",
162
+ "boxShadow",
163
+ "textShadow",
164
+ "opacity",
165
+ "zIndex",
166
+ "width",
167
+ "height",
168
+ "minWidth",
169
+ "minHeight",
170
+ "maxWidth",
171
+ "maxHeight",
172
+ "top",
173
+ "bottom",
174
+ "left",
175
+ "right",
176
+ ]);
177
+
178
+ // ==============================================================================
179
+ // Token 属性 / 检查逻辑
180
+ // ==============================================================================
181
+
182
+ /** kebab-case → camelCase 映射(用于 css`` 模板字符串) */
183
+ const KEBAB_TO_CAMEL = {};
184
+ for (const key of TOKEN_KEYS) {
185
+ const kebab = key.replace(/([A-Z])/g, "-$1").toLowerCase();
186
+ KEBAB_TO_CAMEL[kebab] = key;
187
+ }
188
+
189
+ function isTokenExpr(node) {
190
+ if (!node) return false;
191
+ if (
192
+ node.type === "MemberExpression" &&
193
+ node.object.type === "Identifier" &&
194
+ node.object.name === "token"
195
+ )
196
+ return true;
197
+ if (
198
+ node.type === "CallExpression" &&
199
+ node.callee.type === "Identifier" &&
200
+ ["token", "calc", "max", "min", "clamp"].includes(node.callee.name)
201
+ )
202
+ return true;
203
+ return false;
204
+ }
205
+
206
+ function isAllowedValue(node) {
207
+ if (!node) return true;
208
+ if (isTokenExpr(node)) return true;
209
+ if (node.type === "Literal" && (node.value === 0 || node.value === "0"))
210
+ return true;
211
+ if (
212
+ node.type === "Literal" &&
213
+ typeof node.value === "string" &&
214
+ ALLOWED_LITERALS.has(node.value)
215
+ )
216
+ return true;
217
+ if (
218
+ node.type === "Literal" &&
219
+ typeof node.value === "string" &&
220
+ /^(\d+(\.\d+)?(px|em|rem|%|vh|vw|vmin|vmax|ex|ch|cm|mm|in|pt|pc)|\d+(\.\d+)?\s+(px|em|rem)\s+(solid|dashed|dotted)|\d+\.?\d*%)$/.test(
221
+ node.value,
222
+ )
223
+ )
224
+ return true;
225
+ if (
226
+ [
227
+ "ConditionalExpression",
228
+ "LogicalExpression",
229
+ "BinaryExpression",
230
+ "UnaryExpression",
231
+ "TemplateLiteral",
232
+ "CallExpression",
233
+ "Identifier",
234
+ "SpreadElement",
235
+ ].includes(node.type)
236
+ )
237
+ return true;
238
+ if (node.type === "ObjectExpression" || node.type === "ArrayExpression")
239
+ return true;
240
+ return false;
241
+ }
242
+
243
+ function checkStyleObject(context, objExpr) {
244
+ for (const prop of objExpr.properties) {
245
+ if (prop.type !== "Property") continue;
246
+ const key =
247
+ prop.key.type === "Identifier"
248
+ ? prop.key.name
249
+ : prop.key.type === "Literal"
250
+ ? prop.key.value
251
+ : null;
252
+ if (!key || !TOKEN_KEYS.has(key)) continue;
253
+ const val = prop.value;
254
+ if (isAllowedValue(val)) continue;
255
+ const valText = context.getSourceCode().getText(val);
256
+ context.report({
257
+ node: prop,
258
+ messageId: "noHardcoded",
259
+ data: { key, value: valText },
260
+ });
261
+ }
262
+ }
263
+
264
+ function checkCssTemplate(context, node) {
265
+ const template = node.quasi;
266
+ const quasis = template.quasis;
267
+ const expressions = template.expressions;
268
+
269
+ // 构建完整 CSS 文本,用占位符标记插值位置
270
+ let fullText = "";
271
+ for (let i = 0; i < quasis.length; i++) {
272
+ fullText += quasis[i].value.raw;
273
+ if (i < expressions.length) fullText += "${EXPR}";
274
+ }
275
+
276
+ // 按行分割,逐行检查
277
+ const lines = fullText.split(/\n/);
278
+ for (const line of lines) {
279
+ // 简单解析:找冒号分割的 property: value
280
+ const colonIdx = line.indexOf(":");
281
+ if (colonIdx === -1) continue;
282
+
283
+ const rawProp = line.slice(0, colonIdx).trim();
284
+ const rawVal = line
285
+ .slice(colonIdx + 1)
286
+ .trim()
287
+ .replace(/;$/, "")
288
+ .trim();
289
+
290
+ // 忽略 css 嵌套规则、@media 等
291
+ if (
292
+ rawProp.startsWith("&") ||
293
+ rawProp.startsWith("@") ||
294
+ rawProp.startsWith("//")
295
+ )
296
+ continue;
297
+
298
+ // 匹配 camelCase 或 kebab-case 属性
299
+ const camelProp = TOKEN_KEYS.has(rawProp)
300
+ ? rawProp
301
+ : KEBAB_TO_CAMEL[rawProp];
302
+ if (!camelProp) continue;
303
+
304
+ // 值包含 ${EXPR} → 被插值打断或完全由表达式提供 → 跳过
305
+ if (rawVal.includes("${EXPR}")) continue;
306
+
307
+ // 0 值
308
+ if (rawVal === "0" || rawVal === "0px") continue;
309
+
310
+ // CSS 关键字白名单
311
+ if (ALLOWED_LITERALS.has(rawVal)) continue;
312
+
313
+ // 纯百分比 → 允许(布局常用)
314
+ if (/^\d+\.?\d*%$/.test(rawVal)) continue;
315
+
316
+ // 100vh / 100vw → 允许(全屏布局)
317
+ if (/^100(vh|vw)$/.test(rawVal)) continue;
318
+
319
+ // 百分比 / 带单位字符串 → 硬编码(token 体系也有 sizeStep/sizeUnit)
320
+ if (
321
+ /^(\d+(\.\d+)?(px|em|rem|%|vh|vw|vmin|vmax|ex|ch|cm|mm|in|pt|pc)|\d+\.?\d*%)$/.test(
322
+ rawVal,
323
+ )
324
+ ) {
325
+ context.report({
326
+ node: template,
327
+ messageId: "noHardcoded",
328
+ data: { key: rawProp, value: rawVal },
329
+ });
330
+ continue;
331
+ }
332
+
333
+ // 剩余 → 硬编码
334
+ context.report({
335
+ node: template,
336
+ messageId: "noHardcoded",
337
+ data: { key: rawProp, value: rawVal },
338
+ });
339
+ }
340
+ }
341
+
342
+ /** @type {import('eslint').Rule.RuleModule} */
343
+ const rule = {
344
+ meta: {
345
+ type: "suggestion",
346
+ docs: {
347
+ description:
348
+ "禁止在 style / createStyles / css`` 中硬编码样式值,强制使用 antd token。",
349
+ },
350
+ schema: [],
351
+ messages: {
352
+ noHardcoded:
353
+ "禁止硬编码 `{{ {{key}}: {{value}} }}`。请使用 antd token(如 `token.marginSM`、`token.colorPrimary`)。可参考 .codex/skills/ant-design/SKILL.md 了解 token 体系。",
354
+ },
355
+ },
356
+ create(context) {
357
+ return {
358
+ JSXAttribute(node) {
359
+ if (node.name.name !== "style") return;
360
+ const expr = node.value?.expression;
361
+ if (!expr || expr.type !== "ObjectExpression") return;
362
+ checkStyleObject(context, expr);
363
+ },
364
+
365
+ TaggedTemplateExpression(node) {
366
+ if (node.tag.type === "Identifier" && node.tag.name === "css") {
367
+ checkCssTemplate(context, node);
368
+ }
369
+ },
370
+
371
+ CallExpression(node) {
372
+ if (
373
+ node.callee.type !== "Identifier" ||
374
+ node.callee.name !== "createStyles"
375
+ )
376
+ return;
377
+ const arg = node.arguments[0];
378
+ if (!arg) return;
379
+
380
+ if (arg.type === "ObjectExpression") {
381
+ for (const prop of arg.properties) {
382
+ if (prop.type !== "Property") continue;
383
+ if (prop.value.type === "ObjectExpression")
384
+ checkStyleObject(context, prop.value);
385
+ else if (
386
+ prop.value.type === "TaggedTemplateExpression" &&
387
+ prop.value.tag.name === "css"
388
+ )
389
+ checkCssTemplate(context, prop.value);
390
+ }
391
+ return;
392
+ }
393
+
394
+ let body = null;
395
+ if (arg.type === "ArrowFunctionExpression") body = arg.body;
396
+ else if (arg.type === "FunctionExpression") body = arg.body;
397
+ if (!body) return;
398
+
399
+ if (body.type === "ObjectExpression") {
400
+ for (const prop of body.properties) {
401
+ if (prop.type !== "Property") continue;
402
+ if (prop.value.type === "ObjectExpression")
403
+ checkStyleObject(context, prop.value);
404
+ else if (
405
+ prop.value.type === "TaggedTemplateExpression" &&
406
+ prop.value.tag.name === "css"
407
+ )
408
+ checkCssTemplate(context, prop.value);
409
+ }
410
+ return;
411
+ }
412
+
413
+ if (body.type === "BlockStatement") {
414
+ for (const stmt of body.body) {
415
+ if (
416
+ stmt.type === "ReturnStatement" &&
417
+ stmt.argument?.type === "ObjectExpression"
418
+ ) {
419
+ for (const prop of stmt.argument.properties) {
420
+ if (prop.type !== "Property") continue;
421
+ if (prop.value.type === "ObjectExpression")
422
+ checkStyleObject(context, prop.value);
423
+ else if (
424
+ prop.value.type === "TaggedTemplateExpression" &&
425
+ prop.value.tag.name === "css"
426
+ )
427
+ checkCssTemplate(context, prop.value);
428
+ }
429
+ }
430
+ }
431
+ }
432
+ },
433
+ };
434
+ },
435
+ };
436
+
437
+ export default rule;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * no-one-letter-vars
3
+ * ------------------
4
+ * 禁止单字母变量名(如 `e`、`i`、`g`、`x` 等),要求使用有意义的命名。
5
+ *
6
+ * 例外:
7
+ * - `_` 常用于未使用变量
8
+ * - for 循环的声名部分(for (let i = 0; ...))— 惯用用法
9
+ * - 泛型类型参数如 `<T>`
10
+ */
11
+
12
+ /** @type {import('eslint').Rule.RuleModule} */
13
+ const rule = {
14
+ meta: {
15
+ type: "suggestion",
16
+ docs: {
17
+ description: "禁止使用单字母变量,要求有意义的命名。",
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ noOneLetter: "禁止使用单字母变量名 '{{ name }}',请使用有意义的名称。",
22
+ },
23
+ },
24
+ create(context) {
25
+ // Track function scopes to know if we're at the top level
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
+ let scopeStack = 0;
28
+
29
+ return {
30
+ // --- 变量声名 ---
31
+ VariableDeclarator(node) {
32
+ if (!node.id || node.id.type !== "Identifier") return;
33
+ const name = node.id.name;
34
+ if (name.length !== 1 || !/^[a-z]$/.test(name)) return;
35
+
36
+ // Skip `_`
37
+ if (name === "_") return;
38
+
39
+ // Skip if inside for loop (for (let i = 0..))
40
+ let parent = node.parent;
41
+ while (parent) {
42
+ if (
43
+ parent.type === "ForStatement" ||
44
+ parent.type === "ForInStatement" ||
45
+ parent.type === "ForOfStatement"
46
+ ) {
47
+ return;
48
+ }
49
+ parent = parent.parent;
50
+ }
51
+
52
+ context.report({
53
+ node: node.id,
54
+ messageId: "noOneLetter",
55
+ data: { name },
56
+ });
57
+ },
58
+
59
+ // --- 函数/箭头函数参数 ---
60
+ ":function"(node) {
61
+ scopeStack++;
62
+ for (const param of node.params) {
63
+ if (param.type === "Identifier") {
64
+ const name = param.name;
65
+ if (name.length === 1 && /^[a-z]$/.test(name) && name !== "_") {
66
+ context.report({
67
+ node: param,
68
+ messageId: "noOneLetter",
69
+ data: { name },
70
+ });
71
+ }
72
+ } else if (
73
+ param.type === "ObjectPattern" ||
74
+ param.type === "ArrayPattern"
75
+ ) {
76
+ // Check nested patterns
77
+ checkPattern(param, context);
78
+ }
79
+ }
80
+ },
81
+ ":function:exit"() {
82
+ scopeStack--;
83
+ },
84
+ };
85
+
86
+ function checkPattern(pattern, ctx) {
87
+ for (const prop of pattern.properties || pattern.elements || []) {
88
+ if (!prop) continue;
89
+ const value =
90
+ prop.type === "RestElement" ? prop.argument : prop.value || prop;
91
+ if (value.type === "Identifier") {
92
+ const name = value.name;
93
+ if (name.length === 1 && /^[a-z]$/.test(name) && name !== "_") {
94
+ ctx.report({
95
+ node: value,
96
+ messageId: "noOneLetter",
97
+ data: { name },
98
+ });
99
+ }
100
+ } else if (
101
+ value.type === "ObjectPattern" ||
102
+ value.type === "ArrayPattern"
103
+ ) {
104
+ checkPattern(value, ctx);
105
+ }
106
+ }
107
+ }
108
+ },
109
+ };
110
+
111
+ export default rule;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * no-then
3
+ * -------
4
+ * 禁止使用 .then(),强制使用 async/await。
5
+ *
6
+ * 例外(不检测):
7
+ * - 文件和 /apis/generator/ 下的代码(已有独立 override)
8
+ */
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ const rule = {
12
+ meta: {
13
+ type: "suggestion",
14
+ docs: {
15
+ description: "禁止使用 .then(),必须使用 async/await。",
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ noThen: "禁止使用 .then(),请使用 async/await 替代。",
20
+ },
21
+ },
22
+ create(context) {
23
+ return {
24
+ CallExpression(node) {
25
+ if (
26
+ node.callee.type === "MemberExpression" &&
27
+ node.callee.property.type === "Identifier" &&
28
+ node.callee.property.name === "then"
29
+ ) {
30
+ context.report({
31
+ node: node.callee.property,
32
+ messageId: "noThen",
33
+ });
34
+ }
35
+ },
36
+ };
37
+ },
38
+ };
39
+
40
+ export default rule;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * no-use-request-run
3
+ * -------------------
4
+ * 禁止在 useRequest 解构中使用 `run`(包括 `run: alias` 和 `run`)。
5
+ * 必须使用 `runAsync` 并配合 `await` 在调用处等待。
6
+ */
7
+
8
+ /** @type {import('eslint').Rule.RuleModule} */
9
+ const rule = {
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "禁止使用 useRequest 的 run,必须使用 runAsync + await。",
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ useRunAsync:
18
+ "禁止使用 useRequest 的 run/run:xxx,请使用 runAsync/runAsync:xxx + await。",
19
+ },
20
+ },
21
+ create(context) {
22
+ return {
23
+ VariableDeclarator(node) {
24
+ if (
25
+ node.init &&
26
+ node.init.type === "CallExpression" &&
27
+ node.init.callee.type === "Identifier" &&
28
+ node.init.callee.name === "useRequest"
29
+ ) {
30
+ if (node.id.type === "ObjectPattern") {
31
+ for (const prop of node.id.properties) {
32
+ if (prop.type !== "Property") continue;
33
+ const key = prop.key;
34
+ if (
35
+ key.type === "Identifier" &&
36
+ (key.name === "run" || key.name === "runAsync")
37
+ ) {
38
+ // Only report for `run` / `run: foo`, not for `runAsync`
39
+ if (key.name === "run") {
40
+ context.report({
41
+ node: prop,
42
+ messageId: "useRunAsync",
43
+ });
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ },
50
+ };
51
+ },
52
+ };
53
+
54
+ export default rule;