@ai0x0/utils 0.0.32 → 0.0.34

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.
@@ -187,41 +187,52 @@ for (const key of TOKEN_KEYS) {
187
187
  }
188
188
 
189
189
  function isTokenExpr(node) {
190
- if (!node) return false;
190
+ if (!node) {
191
+ return false;
192
+ }
191
193
  if (
192
194
  node.type === "MemberExpression" &&
193
195
  node.object.type === "Identifier" &&
194
196
  node.object.name === "token"
195
- )
197
+ ) {
196
198
  return true;
199
+ }
197
200
  if (
198
201
  node.type === "CallExpression" &&
199
202
  node.callee.type === "Identifier" &&
200
203
  ["token", "calc", "max", "min", "clamp"].includes(node.callee.name)
201
- )
204
+ ) {
202
205
  return true;
206
+ }
203
207
  return false;
204
208
  }
205
209
 
206
210
  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"))
211
+ if (!node) {
212
+ return true;
213
+ }
214
+ if (isTokenExpr(node)) {
215
+ return true;
216
+ }
217
+ if (node.type === "Literal" && (node.value === 0 || node.value === "0")) {
210
218
  return true;
219
+ }
211
220
  if (
212
221
  node.type === "Literal" &&
213
222
  typeof node.value === "string" &&
214
223
  ALLOWED_LITERALS.has(node.value)
215
- )
224
+ ) {
216
225
  return true;
226
+ }
217
227
  if (
218
228
  node.type === "Literal" &&
219
229
  typeof node.value === "string" &&
220
230
  /^(\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
231
  node.value,
222
232
  )
223
- )
233
+ ) {
224
234
  return true;
235
+ }
225
236
  if (
226
237
  [
227
238
  "ConditionalExpression",
@@ -233,25 +244,33 @@ function isAllowedValue(node) {
233
244
  "Identifier",
234
245
  "SpreadElement",
235
246
  ].includes(node.type)
236
- )
247
+ ) {
237
248
  return true;
238
- if (node.type === "ObjectExpression" || node.type === "ArrayExpression")
249
+ }
250
+ if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
239
251
  return true;
252
+ }
240
253
  return false;
241
254
  }
242
255
 
243
256
  function checkStyleObject(context, objExpr) {
244
257
  for (const prop of objExpr.properties) {
245
- if (prop.type !== "Property") continue;
258
+ if (prop.type !== "Property") {
259
+ continue;
260
+ }
246
261
  const key =
247
262
  prop.key.type === "Identifier"
248
263
  ? prop.key.name
249
264
  : prop.key.type === "Literal"
250
265
  ? prop.key.value
251
266
  : null;
252
- if (!key || !TOKEN_KEYS.has(key)) continue;
267
+ if (!key || !TOKEN_KEYS.has(key)) {
268
+ continue;
269
+ }
253
270
  const val = prop.value;
254
- if (isAllowedValue(val)) continue;
271
+ if (isAllowedValue(val)) {
272
+ continue;
273
+ }
255
274
  const valText = context.getSourceCode().getText(val);
256
275
  context.report({
257
276
  node: prop,
@@ -270,7 +289,9 @@ function checkCssTemplate(context, node) {
270
289
  let fullText = "";
271
290
  for (let i = 0; i < quasis.length; i++) {
272
291
  fullText += quasis[i].value.raw;
273
- if (i < expressions.length) fullText += "${EXPR}";
292
+ if (i < expressions.length) {
293
+ fullText += "${EXPR}";
294
+ }
274
295
  }
275
296
 
276
297
  // 按行分割,逐行检查
@@ -278,7 +299,9 @@ function checkCssTemplate(context, node) {
278
299
  for (const line of lines) {
279
300
  // 简单解析:找冒号分割的 property: value
280
301
  const colonIdx = line.indexOf(":");
281
- if (colonIdx === -1) continue;
302
+ if (colonIdx === -1) {
303
+ continue;
304
+ }
282
305
 
283
306
  const rawProp = line.slice(0, colonIdx).trim();
284
307
  const rawVal = line
@@ -292,29 +315,42 @@ function checkCssTemplate(context, node) {
292
315
  rawProp.startsWith("&") ||
293
316
  rawProp.startsWith("@") ||
294
317
  rawProp.startsWith("//")
295
- )
318
+ ) {
296
319
  continue;
320
+ }
297
321
 
298
322
  // 匹配 camelCase 或 kebab-case 属性
299
323
  const camelProp = TOKEN_KEYS.has(rawProp)
300
324
  ? rawProp
301
325
  : KEBAB_TO_CAMEL[rawProp];
302
- if (!camelProp) continue;
326
+ if (!camelProp) {
327
+ continue;
328
+ }
303
329
 
304
330
  // 值包含 ${EXPR} → 被插值打断或完全由表达式提供 → 跳过
305
- if (rawVal.includes("${EXPR}")) continue;
331
+ if (rawVal.includes("${EXPR}")) {
332
+ continue;
333
+ }
306
334
 
307
335
  // 0 值
308
- if (rawVal === "0" || rawVal === "0px") continue;
336
+ if (rawVal === "0" || rawVal === "0px") {
337
+ continue;
338
+ }
309
339
 
310
340
  // CSS 关键字白名单
311
- if (ALLOWED_LITERALS.has(rawVal)) continue;
341
+ if (ALLOWED_LITERALS.has(rawVal)) {
342
+ continue;
343
+ }
312
344
 
313
345
  // 纯百分比 → 允许(布局常用)
314
- if (/^\d+\.?\d*%$/.test(rawVal)) continue;
346
+ if (/^\d+\.?\d*%$/.test(rawVal)) {
347
+ continue;
348
+ }
315
349
 
316
350
  // 100vh / 100vw → 允许(全屏布局)
317
- if (/^100(vh|vw)$/.test(rawVal)) continue;
351
+ if (/^100(vh|vw)$/.test(rawVal)) {
352
+ continue;
353
+ }
318
354
 
319
355
  // 百分比 / 带单位字符串 → 硬编码(token 体系也有 sizeStep/sizeUnit)
320
356
  if (
@@ -339,6 +375,10 @@ function checkCssTemplate(context, node) {
339
375
  }
340
376
  }
341
377
 
378
+ // ==============================================================================
379
+ // ESLint 规则
380
+ // ==============================================================================
381
+
342
382
  /** @type {import('eslint').Rule.RuleModule} */
343
383
  const rule = {
344
384
  meta: {
@@ -350,15 +390,19 @@ const rule = {
350
390
  schema: [],
351
391
  messages: {
352
392
  noHardcoded:
353
- "禁止硬编码 `{{ {{key}}: {{value}} }}`。请使用 antd token(如 `token.marginSM`、`token.colorPrimary`)。可参考 .codex/skills/ant-design/SKILL.md 了解 token 体系。",
393
+ "禁止硬编码 `{{ {{key}}: {{value}} }}`。请使用 antd token(如 `token.marginSM`、`token.colorPrimary`)。可参考 node_modules/@ai0x0/utils/.agents/skills/antd/SKILL.md 了解 token 体系。",
354
394
  },
355
395
  },
356
396
  create(context) {
357
397
  return {
358
398
  JSXAttribute(node) {
359
- if (node.name.name !== "style") return;
399
+ if (node.name.name !== "style") {
400
+ return;
401
+ }
360
402
  const expr = node.value?.expression;
361
- if (!expr || expr.type !== "ObjectExpression") return;
403
+ if (!expr || expr.type !== "ObjectExpression") {
404
+ return;
405
+ }
362
406
  checkStyleObject(context, expr);
363
407
  },
364
408
 
@@ -372,40 +416,54 @@ const rule = {
372
416
  if (
373
417
  node.callee.type !== "Identifier" ||
374
418
  node.callee.name !== "createStyles"
375
- )
419
+ ) {
376
420
  return;
421
+ }
377
422
  const arg = node.arguments[0];
378
- if (!arg) return;
423
+ if (!arg) {
424
+ return;
425
+ }
379
426
 
380
427
  if (arg.type === "ObjectExpression") {
381
428
  for (const prop of arg.properties) {
382
- if (prop.type !== "Property") continue;
383
- if (prop.value.type === "ObjectExpression")
429
+ if (prop.type !== "Property") {
430
+ continue;
431
+ }
432
+ if (prop.value.type === "ObjectExpression") {
384
433
  checkStyleObject(context, prop.value);
385
- else if (
434
+ } else if (
386
435
  prop.value.type === "TaggedTemplateExpression" &&
387
436
  prop.value.tag.name === "css"
388
- )
437
+ ) {
389
438
  checkCssTemplate(context, prop.value);
439
+ }
390
440
  }
391
441
  return;
392
442
  }
393
443
 
394
444
  let body = null;
395
- if (arg.type === "ArrowFunctionExpression") body = arg.body;
396
- else if (arg.type === "FunctionExpression") body = arg.body;
397
- if (!body) return;
445
+ if (arg.type === "ArrowFunctionExpression") {
446
+ body = arg.body;
447
+ } else if (arg.type === "FunctionExpression") {
448
+ body = arg.body;
449
+ }
450
+ if (!body) {
451
+ return;
452
+ }
398
453
 
399
454
  if (body.type === "ObjectExpression") {
400
455
  for (const prop of body.properties) {
401
- if (prop.type !== "Property") continue;
402
- if (prop.value.type === "ObjectExpression")
456
+ if (prop.type !== "Property") {
457
+ continue;
458
+ }
459
+ if (prop.value.type === "ObjectExpression") {
403
460
  checkStyleObject(context, prop.value);
404
- else if (
461
+ } else if (
405
462
  prop.value.type === "TaggedTemplateExpression" &&
406
463
  prop.value.tag.name === "css"
407
- )
464
+ ) {
408
465
  checkCssTemplate(context, prop.value);
466
+ }
409
467
  }
410
468
  return;
411
469
  }
@@ -417,14 +475,17 @@ const rule = {
417
475
  stmt.argument?.type === "ObjectExpression"
418
476
  ) {
419
477
  for (const prop of stmt.argument.properties) {
420
- if (prop.type !== "Property") continue;
421
- if (prop.value.type === "ObjectExpression")
478
+ if (prop.type !== "Property") {
479
+ continue;
480
+ }
481
+ if (prop.value.type === "ObjectExpression") {
422
482
  checkStyleObject(context, prop.value);
423
- else if (
483
+ } else if (
424
484
  prop.value.type === "TaggedTemplateExpression" &&
425
485
  prop.value.tag.name === "css"
426
- )
486
+ ) {
427
487
  checkCssTemplate(context, prop.value);
488
+ }
428
489
  }
429
490
  }
430
491
  }
@@ -9,7 +9,7 @@
9
9
  * - 550 ~ 749 行:至少 3 个分区块
10
10
  * 以此类推,每 200 行增加 1 个分区块。
11
11
  *
12
- * 分隔注释块格式(见 .agents/skills/dev-style/SKILL.md):
12
+ * 分隔注释块格式(见 node_modules/@ai0x0/utils/.agents/skills/nextjs/SKILL.md):
13
13
  *
14
14
  * // ==============================================================================
15
15
  * // <中文标题>
@@ -22,24 +22,39 @@ const DIVIDER_EQ = /^\s*={4,}\s*$/;
22
22
 
23
23
  /** 相邻两行注释 + 之间中间一行文字:判定一个三行注释块为分隔块。 */
24
24
  function isDividerBlock(commentA, commentB, commentC) {
25
- if (!commentA || !commentB || !commentC) return false;
25
+ if (!commentA || !commentB || !commentC) {
26
+ return false;
27
+ }
26
28
  if (
27
29
  commentA.type !== "Line" ||
28
30
  commentB.type !== "Line" ||
29
31
  commentC.type !== "Line"
30
- )
32
+ ) {
33
+ return false;
34
+ }
35
+ if (!DIVIDER_EQ.test(commentA.value)) {
36
+ return false;
37
+ }
38
+ if (!DIVIDER_EQ.test(commentC.value)) {
39
+ return false;
40
+ }
41
+ if (!commentB.value.trim()) {
31
42
  return false;
32
- if (!DIVIDER_EQ.test(commentA.value)) return false;
33
- if (!DIVIDER_EQ.test(commentC.value)) return false;
34
- if (!commentB.value.trim()) return false;
35
- if (commentA.loc.end.line + 1 !== commentB.loc.start.line) return false;
36
- if (commentB.loc.end.line + 1 !== commentC.loc.start.line) return false;
43
+ }
44
+ if (commentA.loc.end.line + 1 !== commentB.loc.start.line) {
45
+ return false;
46
+ }
47
+ if (commentB.loc.end.line + 1 !== commentC.loc.start.line) {
48
+ return false;
49
+ }
37
50
  return true;
38
51
  }
39
52
 
40
53
  /** 计算文件需要多少个分区块(每 150 行 1 个,从 150 行起)。 */
41
54
  function requiredDividers(totalLines, minLines) {
42
- if (totalLines < minLines) return 0;
55
+ if (totalLines < minLines) {
56
+ return 0;
57
+ }
43
58
  // 150-299: 1个, 300-449: 2个, 依此类推
44
59
  return Math.floor(totalLines / minLines);
45
60
  }
@@ -74,10 +89,14 @@ const rule = {
74
89
  return {
75
90
  Program(program) {
76
91
  const totalLines = program.loc.end.line;
77
- if (totalLines < minLines) return;
92
+ if (totalLines < minLines) {
93
+ return;
94
+ }
78
95
 
79
96
  const required = requiredDividers(totalLines, minLines);
80
- if (required <= 0) return;
97
+ if (required <= 0) {
98
+ return;
99
+ }
81
100
 
82
101
  const allComments = sourceCode.getAllComments();
83
102
 
@@ -23,7 +23,7 @@ const rule = {
23
23
  schema: [],
24
24
  messages: {
25
25
  useFormRequired:
26
- "检测到表单场景使用了 useState/useSetState 管理字段值。请改用 Form.useForm() 或 rc-field-form 的 useForm。详见 .agents/skills/form-convention/SKILL.md。",
26
+ "检测到表单场景使用了 useState/useSetState 管理字段值。请改用 Form.useForm() 或 rc-field-form 的 useForm。详见 node_modules/@ai0x0/utils/.agents/skills/antd/SKILL.md。",
27
27
  },
28
28
  },
29
29
  create(context) {
@@ -39,16 +39,23 @@ const rule = {
39
39
 
40
40
  return {
41
41
  JSXOpeningElement(node) {
42
- if (hasFormPattern) return;
42
+ if (hasFormPattern) {
43
+ return;
44
+ }
43
45
 
44
46
  let hasValue = false;
45
47
  let hasOnChange = false;
46
48
 
47
49
  for (const attr of node.attributes) {
48
- if (attr.type !== "JSXAttribute" || !attr.name) continue;
50
+ if (attr.type !== "JSXAttribute" || !attr.name) {
51
+ continue;
52
+ }
49
53
  const attrName = attr.name.name;
50
- if (attrName === "value") hasValue = true;
51
- else if (attrName === "onChange") hasOnChange = true;
54
+ if (attrName === "value") {
55
+ hasValue = true;
56
+ } else if (attrName === "onChange") {
57
+ hasOnChange = true;
58
+ }
52
59
 
53
60
  if (hasValue && hasOnChange) {
54
61
  hasFormPattern = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai0x0/utils",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "AI0x0 utils",
5
5
  "keywords": [
6
6
  "ai0x0"
@@ -24,6 +24,7 @@
24
24
  "lib",
25
25
  "eslint-config",
26
26
  "eslint-rules",
27
+ ".agents",
27
28
  "SKILL.md"
28
29
  ],
29
30
  "scripts": {