@nest-boot/eslint-plugin 7.0.3 → 7.0.5

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 (44) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/rules/graphql/graphql-field-config-from-types.d.ts +3 -1
  4. package/dist/rules/graphql/graphql-field-config-from-types.js +36 -36
  5. package/dist/rules/graphql/graphql-field-config-from-types.js.map +1 -1
  6. package/dist/rules/graphql/graphql-field-definite-assignment.d.ts +3 -1
  7. package/dist/rules/graphql/graphql-field-definite-assignment.js +13 -13
  8. package/dist/rules/graphql/graphql-field-definite-assignment.js.map +1 -1
  9. package/dist/rules/import/import-bullmq.d.ts +3 -1
  10. package/dist/rules/import/import-bullmq.js +4 -4
  11. package/dist/rules/import/import-bullmq.js.map +1 -1
  12. package/dist/rules/import/import-graphql.d.ts +3 -1
  13. package/dist/rules/import/import-graphql.js +4 -4
  14. package/dist/rules/import/import-graphql.js.map +1 -1
  15. package/dist/rules/import/import-mikro-orm.d.ts +3 -1
  16. package/dist/rules/import/import-mikro-orm.js +4 -4
  17. package/dist/rules/import/import-mikro-orm.js.map +1 -1
  18. package/dist/rules/index.d.ts +21 -7
  19. package/dist/rules/mikro-orm/entity-field-definite-assignment.d.ts +3 -1
  20. package/dist/rules/mikro-orm/entity-field-definite-assignment.js +12 -10
  21. package/dist/rules/mikro-orm/entity-field-definite-assignment.js.map +1 -1
  22. package/dist/rules/mikro-orm/entity-property-config-from-types.d.ts +3 -1
  23. package/dist/rules/mikro-orm/entity-property-config-from-types.js +149 -116
  24. package/dist/rules/mikro-orm/entity-property-config-from-types.js.map +1 -1
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/dist/utils/createRule.d.ts +3 -1
  27. package/dist/utils/decorators.d.ts +16 -16
  28. package/dist/utils/decorators.js +16 -16
  29. package/package.json +12 -7
  30. package/src/rules/graphql/graphql-field-config-from-types.spec.ts +18 -18
  31. package/src/rules/graphql/graphql-field-config-from-types.ts +37 -37
  32. package/src/rules/graphql/graphql-field-definite-assignment.spec.ts +11 -11
  33. package/src/rules/graphql/graphql-field-definite-assignment.ts +13 -13
  34. package/src/rules/import/import-bullmq.spec.ts +9 -9
  35. package/src/rules/import/import-bullmq.ts +5 -4
  36. package/src/rules/import/import-graphql.spec.ts +8 -8
  37. package/src/rules/import/import-graphql.ts +4 -4
  38. package/src/rules/import/import-mikro-orm.spec.ts +8 -8
  39. package/src/rules/import/import-mikro-orm.ts +4 -4
  40. package/src/rules/mikro-orm/entity-field-definite-assignment.spec.ts +18 -18
  41. package/src/rules/mikro-orm/entity-field-definite-assignment.ts +12 -10
  42. package/src/rules/mikro-orm/entity-property-config-from-types.spec.ts +24 -24
  43. package/src/rules/mikro-orm/entity-property-config-from-types.ts +171 -117
  44. package/src/utils/decorators.ts +16 -16
@@ -9,7 +9,7 @@ import * as ts from "typescript";
9
9
  import { createRule } from "../../utils/createRule";
10
10
  import { hasClassDecorator } from "../../utils/decorators";
11
11
 
12
- // 自定义 Fix 对象类型,用于延迟应用修复
12
+ // Custom Fix object type for deferred fix application
13
13
  interface CustomFix {
14
14
  type: "insert" | "replace";
15
15
  range: readonly [number, number];
@@ -20,9 +20,9 @@ interface TypeInfo {
20
20
  typeName: string | null;
21
21
  isArray: boolean;
22
22
  isNullable: boolean;
23
- isEnum: boolean; // 是否是枚举类型
24
- propertyType: string | null; // MikroORM 类型如 t.text, t.bigint
25
- arrayElementTypeName?: string | null; // 数组元素的类型名称(用于特殊验证)
23
+ isEnum: boolean; // Whether the type is an enum
24
+ propertyType: string | null; // MikroORM type such as t.text, t.bigint, etc.
25
+ arrayElementTypeName?: string | null; // Type name of array elements (for special validation)
26
26
  }
27
27
 
28
28
  export default createRule<
@@ -38,20 +38,21 @@ export default createRule<
38
38
  type: "problem",
39
39
  docs: {
40
40
  description:
41
- "根据 TypeScript 类型自动生成或修正 @Property 装饰器的类型与 nullable 配置(支持数组),以及 @Enum 装饰器的 nullable 配置。检查有初始化值的属性是否使用 Opt<T> 类型。",
41
+ "Automatically generate or fix @Property decorator type and nullable configuration based on TypeScript types (with array support), as well as @Enum decorator nullable configuration. Checks whether properties with initializers use the Opt<T> type.",
42
42
  },
43
43
  fixable: "code",
44
44
  schema: [],
45
45
  messages: {
46
46
  alignPropertyDecoratorWithTsType:
47
- "@Property 装饰器应与 TypeScript 类型保持一致(类型与 nullable)。",
47
+ "@Property decorator should align with the TypeScript type (type and nullable).",
48
48
  removePropertyDecorator:
49
- "属性带有 @{{decoratorName}} 装饰器,应移除 @Property 装饰器。",
50
- useEnumDecorator: "枚举类型应使用 @Enum 装饰器而不是 @Property 装饰器。",
49
+ "Property has a @{{decoratorName}} decorator, @Property decorator should be removed.",
50
+ useEnumDecorator:
51
+ "Enum types should use @Enum decorator instead of @Property decorator.",
51
52
  useOptTypeForInitializedProperty:
52
- "有非 null 初始化值的属性应使用 Opt<T> 类型包装。",
53
+ "Properties with non-null initializers should use the Opt<T> type wrapper.",
53
54
  removeOptTypeForNonInitializedProperty:
54
- "没有初始化值或初始化为 null 的属性不应使用 Opt<T> 类型包装。",
55
+ "Properties without initializers or initialized to null should not use the Opt<T> type wrapper.",
55
56
  },
56
57
  },
57
58
  defaultOptions: [],
@@ -60,18 +61,18 @@ export default createRule<
60
61
  const parserServices = ESLintUtils.getParserServices(context);
61
62
  const checker = parserServices.program.getTypeChecker();
62
63
 
63
- // 检查是否为枚举类型
64
+ // Check if the type is an enum
64
65
  const isEnumType = (node: TSESTree.Node): boolean => {
65
66
  try {
66
67
  const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
67
68
  const type = checker.getTypeAtLocation(tsNode);
68
69
 
69
- // 检查是否是枚举类型
70
+ // Check if it is an enum type
70
71
  if (type.symbol.flags & ts.SymbolFlags.Enum) {
71
72
  return true;
72
73
  }
73
74
 
74
- // 检查联合类型中的每个成员
75
+ // Check each member in a union type
75
76
  if (type.isUnion()) {
76
77
  return type.types.some(
77
78
  (t) =>
@@ -92,15 +93,15 @@ export default createRule<
92
93
  switch (typeName) {
93
94
  case "String":
94
95
  case "string":
95
- return "t.string"; // 默认使用 t.string
96
+ return "t.string"; // Default to t.string
96
97
  case "Number":
97
98
  case "number":
98
- return "t.float"; // 默认使用 t.float
99
+ return "t.float"; // Default to t.float
99
100
  case "Boolean":
100
101
  case "boolean":
101
- return "t.boolean"; // 默认使用 t.boolean
102
+ return "t.boolean"; // Default to t.boolean
102
103
  case "Date":
103
- return "t.datetime"; // 默认使用 t.datetime
104
+ return "t.datetime"; // Default to t.datetime
104
105
  case "GraphQLJSONObject":
105
106
  case "Record":
106
107
  return "t.json";
@@ -112,35 +113,36 @@ export default createRule<
112
113
  const isValidStringType = (typeConfig: string | null): boolean => {
113
114
  if (!typeConfig) return false;
114
115
 
115
- // 接受 t.string, t.text, t.decimal, t.bigint
116
+ // Accept t.string, t.text, t.uuid, t.decimal, t.bigint
116
117
  if (
117
118
  typeConfig === "t.string" ||
118
119
  typeConfig === "t.text" ||
120
+ typeConfig === "t.uuid" ||
119
121
  typeConfig === "t.decimal" ||
120
122
  typeConfig === "t.bigint"
121
123
  ) {
122
124
  return true;
123
125
  }
124
126
 
125
- // 接受 DecimalType(类引用,不带参数)
127
+ // Accept DecimalType (class reference, without arguments)
126
128
  if (typeConfig === "DecimalType") {
127
129
  return true;
128
130
  }
129
131
 
130
- // 接受 new DecimalType('string') new BigIntType('string')
131
- // 但排除 new BigIntType('number')
132
+ // Accept new DecimalType('string') and new BigIntType('string')
133
+ // but exclude new BigIntType('number')
132
134
  if (
133
135
  typeConfig.includes("DecimalType") ||
134
136
  typeConfig.includes("BigIntType")
135
137
  ) {
136
- // 如果是 new BigIntType('number'),则无效
138
+ // If it's new BigIntType('number'), it's invalid
137
139
  if (
138
140
  typeConfig.includes("BigIntType") &&
139
141
  (typeConfig.includes("'number'") || typeConfig.includes('"number"'))
140
142
  ) {
141
143
  return false;
142
144
  }
143
- // 其他情况(包括 new XXXType('string') DecimalType)都有效
145
+ // Other cases (including new XXXType('string') and DecimalType) are valid
144
146
  return true;
145
147
  }
146
148
 
@@ -150,7 +152,7 @@ export default createRule<
150
152
  const isValidNumberType = (typeConfig: string | null): boolean => {
151
153
  if (!typeConfig) return false;
152
154
 
153
- // 接受 t.integer, t.float, t.double, t.decimal
155
+ // Accept t.integer, t.float, t.double, t.decimal
154
156
  if (
155
157
  typeConfig === "t.integer" ||
156
158
  typeConfig === "t.float" ||
@@ -160,25 +162,25 @@ export default createRule<
160
162
  return true;
161
163
  }
162
164
 
163
- // 接受 BigIntType DecimalType(类引用,不带参数)
165
+ // Accept BigIntType and DecimalType (class reference, without arguments)
164
166
  if (typeConfig === "BigIntType" || typeConfig === "DecimalType") {
165
167
  return true;
166
168
  }
167
169
 
168
- // 接受 new DecimalType('number') new BigIntType('number')
169
- // 但排除 new XXXType('string')
170
+ // Accept new DecimalType('number') and new BigIntType('number')
171
+ // but exclude new XXXType('string')
170
172
  if (
171
173
  typeConfig.includes("DecimalType") ||
172
174
  typeConfig.includes("BigIntType")
173
175
  ) {
174
- // 如果包含 'string',则对 number 类型无效
176
+ // If it contains 'string', it's invalid for number type
175
177
  if (
176
178
  typeConfig.includes("'string'") ||
177
179
  typeConfig.includes('"string"')
178
180
  ) {
179
181
  return false;
180
182
  }
181
- // 其他情况(包括 new XXXType('number'))都有效
183
+ // Other cases (including new XXXType('number')) are valid
182
184
  return true;
183
185
  }
184
186
 
@@ -188,17 +190,17 @@ export default createRule<
188
190
  const isValidNumberArrayType = (typeConfig: string | null): boolean => {
189
191
  if (!typeConfig) return false;
190
192
 
191
- // 接受 t.array(默认)
193
+ // Accept t.array (default)
192
194
  if (typeConfig === "t.array") {
193
195
  return true;
194
196
  }
195
197
 
196
- // 接受 VectorType(类引用)
198
+ // Accept VectorType (class reference)
197
199
  if (typeConfig === "VectorType") {
198
200
  return true;
199
201
  }
200
202
 
201
- // 接受 new VectorType(...)
203
+ // Accept new VectorType(...)
202
204
  if (typeConfig.includes("VectorType")) {
203
205
  return true;
204
206
  }
@@ -209,7 +211,7 @@ export default createRule<
209
211
  const isValidDateType = (typeConfig: string | null): boolean => {
210
212
  if (!typeConfig) return false;
211
213
 
212
- // 接受 t.datetime, t.date, t.time
214
+ // Accept t.datetime, t.date, t.time
213
215
  if (
214
216
  typeConfig === "t.datetime" ||
215
217
  typeConfig === "t.date" ||
@@ -255,7 +257,7 @@ export default createRule<
255
257
  );
256
258
  };
257
259
 
258
- // 检查类型是否被 Opt<T> 包装
260
+ // Check if the type is wrapped with Opt<T>
259
261
  const isWrappedWithOpt = (
260
262
  property: TSESTree.PropertyDefinition,
261
263
  ): boolean => {
@@ -272,13 +274,13 @@ export default createRule<
272
274
  );
273
275
  };
274
276
 
275
- // 检查是否已导入 Opt
277
+ // Check if Opt is imported
276
278
  const hasOptImport = (): boolean => {
277
279
  const program = context.sourceCode.ast;
278
280
  for (const statement of program.body) {
279
281
  if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
280
282
  const importSource = statement.source.value;
281
- // 检查从 @mikro-orm/* 导入的语句
283
+ // Check imports from @mikro-orm/*
282
284
  if (
283
285
  typeof importSource === "string" &&
284
286
  importSource.startsWith("@mikro-orm/")
@@ -299,11 +301,11 @@ export default createRule<
299
301
  return false;
300
302
  };
301
303
 
302
- // 添加 Opt @mikro-orm/core 的导入
304
+ // Add Opt to the @mikro-orm/core import
303
305
  const addOptImport = (fixer: RuleFixer): RuleFix | null => {
304
306
  const program = context.sourceCode.ast;
305
307
 
306
- // 查找 @mikro-orm/core 的导入语句
308
+ // Find the @mikro-orm/core import statement
307
309
  let coreImport: TSESTree.ImportDeclaration | null = null;
308
310
 
309
311
  for (const statement of program.body) {
@@ -317,24 +319,24 @@ export default createRule<
317
319
  }
318
320
 
319
321
  if (coreImport) {
320
- // 已有 @mikro-orm/core 导入,添加 Opt 到导入列表
322
+ // Already has @mikro-orm/core import, add Opt to the import list
321
323
  const lastSpecifier =
322
324
  coreImport.specifiers[coreImport.specifiers.length - 1];
323
325
 
324
- // 检查是否是多行导入
326
+ // Check if it's a multiline import
325
327
  const importText = source.getText(coreImport);
326
328
  const isMultiline = importText.includes("\n");
327
329
 
328
330
  if (isMultiline) {
329
- // 多行导入:在最后一个导入项后添加,保持缩进
330
- const indent = " "; // 假设使用 2 个空格缩进
331
+ // Multiline import: add after the last import item, keeping indentation
332
+ const indent = " "; // Assuming 2-space indentation
331
333
  return fixer.insertTextAfter(lastSpecifier, `,\n${indent}Opt`);
332
334
  } else {
333
- // 单行导入:直接添加
335
+ // Single-line import: add directly
334
336
  return fixer.insertTextAfter(lastSpecifier, ", Opt");
335
337
  }
336
338
  } else {
337
- // 没有 @mikro-orm/core 导入,在文件开头添加新的导入语句
339
+ // No @mikro-orm/core import, add a new import statement at the top
338
340
  const firstImport = program.body.find(
339
341
  (node: TSESTree.ProgramStatement) =>
340
342
  node.type === AST_NODE_TYPES.ImportDeclaration,
@@ -362,12 +364,12 @@ export default createRule<
362
364
  ? property.typeAnnotation.typeAnnotation
363
365
  : null;
364
366
 
365
- // 可选属性(?)视为可空
367
+ // Optional property (?) is treated as nullable
366
368
  if (property.optional) {
367
369
  isNullable = true;
368
370
  }
369
371
 
370
- // 处理联合类型中的 null/undefined
372
+ // Handle null/undefined in union types
371
373
  if (baseTypeNode?.type === AST_NODE_TYPES.TSUnionType) {
372
374
  const hasNullish = baseTypeNode.types.some((t: TSESTree.TypeNode) => {
373
375
  return (
@@ -386,7 +388,7 @@ export default createRule<
386
388
  }) ?? null;
387
389
  }
388
390
 
389
- // 先解包 Ref<T> Opt<T> → T,并在内部再次处理 null/undefined
391
+ // First unwrap Ref<T> and Opt<T> → T, and handle null/undefined within
390
392
  if (
391
393
  baseTypeNode?.type === AST_NODE_TYPES.TSTypeReference &&
392
394
  baseTypeNode.typeName.type === AST_NODE_TYPES.Identifier &&
@@ -413,12 +415,12 @@ export default createRule<
413
415
  baseTypeNode = inner ?? baseTypeNode;
414
416
  }
415
417
 
416
- // 跳过 Collection<T> 类型(这些应该由 OneToMany 等装饰器处理)
418
+ // Skip Collection<T> types (these should be handled by OneToMany etc. decorators)
417
419
  if (baseTypeNode && isCollectionType(baseTypeNode)) {
418
420
  return null;
419
421
  }
420
422
 
421
- // 数组类型(T[] Array<T>)- 在解包 Opt/Ref 之后检查
423
+ // Array type (T[] or Array<T>) - checked after unwrapping Opt/Ref
422
424
  const elementTypeNode = baseTypeNode
423
425
  ? extractArrayElementType(baseTypeNode)
424
426
  : null;
@@ -429,12 +431,12 @@ export default createRule<
429
431
  const targetTypeNode: TSESTree.TypeNode | null =
430
432
  elementTypeNode ?? baseTypeNode;
431
433
 
432
- // 检查目标类型是否为枚举
434
+ // Check if target type is an enum
433
435
  if (targetTypeNode) {
434
436
  isEnum = isEnumType(targetTypeNode);
435
437
  }
436
438
 
437
- // 无显式类型时,尝试从字面量初始值推断
439
+ // When no explicit type, try to infer from literal initializer
438
440
  if (!targetTypeNode) {
439
441
  if (property.value?.type === AST_NODE_TYPES.Literal) {
440
442
  const value = property.value.value;
@@ -486,7 +488,7 @@ export default createRule<
486
488
  };
487
489
  }
488
490
 
489
- // 关键字类型
491
+ // Keyword types
490
492
  if (targetTypeNode.type === AST_NODE_TYPES.TSStringKeyword) {
491
493
  return {
492
494
  typeName: "string",
@@ -518,10 +520,10 @@ export default createRule<
518
520
  };
519
521
  }
520
522
 
521
- // 标识符(类/自定义类型)
523
+ // Identifier (class/custom type)
522
524
  const ident = getIdentifierName(targetTypeNode);
523
525
  if (ident) {
524
- // 如果是数组,自定义类型数组使用 t.json
526
+ // For arrays, custom type arrays use t.json
525
527
  if (isArray) {
526
528
  return {
527
529
  typeName: ident,
@@ -546,7 +548,7 @@ export default createRule<
546
548
  return null;
547
549
  };
548
550
 
549
- // 将类型包装为 Opt<T>
551
+ // Wrap type with Opt<T>
550
552
  const wrapWithOpt = (typeString: string): string => {
551
553
  return `Opt<${typeString}>`;
552
554
  };
@@ -572,15 +574,16 @@ export default createRule<
572
574
  const buildPropertyDecorator = (
573
575
  info: TypeInfo,
574
576
  otherProps: { key: string; value: string }[] = [],
577
+ decoratorName = "Property",
575
578
  ): string => {
576
579
  const options: string[] = [];
577
580
 
578
- // 如果有 propertyType 配置,添加 type
581
+ // If there is a propertyType configuration, add type
579
582
  if (info.propertyType) {
580
583
  options.push(`type: ${info.propertyType}`);
581
584
  }
582
585
 
583
- // 添加其他属性(保持原有顺序)
586
+ // Add other properties (keeping original order)
584
587
  for (const prop of otherProps) {
585
588
  options.push(`${prop.key}: ${prop.value}`);
586
589
  }
@@ -590,10 +593,10 @@ export default createRule<
590
593
  }
591
594
 
592
595
  if (options.length === 0) {
593
- return "@Property()";
596
+ return `@${decoratorName}()`;
594
597
  }
595
598
 
596
- return `@Property({ ${options.join(", ")} })`;
599
+ return `@${decoratorName}({ ${options.join(", ")} })`;
597
600
  };
598
601
 
599
602
  const addPropertyDecorator = (
@@ -622,42 +625,54 @@ export default createRule<
622
625
  nullable: boolean;
623
626
  otherProps: { key: string; value: string }[];
624
627
  },
628
+ decoratorName = "Property",
625
629
  ) => {
626
630
  const fixes: CustomFix[] = [];
627
631
 
628
- // 如果当前已经有有效的配置,保留它而不是替换成默认值
632
+ // If current config already has a valid value, keep it instead of replacing with default
629
633
  const finalInfo = { ...info };
634
+
635
+ // PrimaryKey number type defaults to t.integer
636
+ if (
637
+ decoratorName === "PrimaryKey" &&
638
+ finalInfo.propertyType === "t.float"
639
+ ) {
640
+ finalInfo.propertyType = "t.integer";
641
+ }
642
+
630
643
  if (
631
644
  info.propertyType === "t.string" &&
632
645
  isValidStringType(currentConfig.type)
633
646
  ) {
634
- // 保留有效的 string 类型配置
647
+ // Keep valid string type config
635
648
  finalInfo.propertyType = currentConfig.type;
636
649
  } else if (
637
- info.propertyType === "t.float" &&
650
+ (info.propertyType === "t.float" ||
651
+ info.propertyType === "t.integer") &&
638
652
  isValidNumberType(currentConfig.type)
639
653
  ) {
640
- // 保留有效的 number 类型配置
654
+ // Keep valid number type config
641
655
  finalInfo.propertyType = currentConfig.type;
642
656
  } else if (
643
657
  info.propertyType === "t.datetime" &&
644
658
  isValidDateType(currentConfig.type)
645
659
  ) {
646
- // 保留有效的 Date 类型配置
660
+ // Keep valid Date type config
647
661
  finalInfo.propertyType = currentConfig.type;
648
662
  } else if (
649
663
  info.isArray &&
650
664
  info.arrayElementTypeName === "number" &&
651
665
  isValidNumberArrayType(currentConfig.type)
652
666
  ) {
653
- // 保留有效的 number[] 类型配置(VectorType
667
+ // Keep valid number[] type config (VectorType)
654
668
  finalInfo.propertyType = currentConfig.type;
655
669
  }
656
670
 
657
- // 保留其他属性配置
671
+ // Keep other property configs
658
672
  const newDecoratorText = buildPropertyDecorator(
659
673
  finalInfo,
660
674
  currentConfig.otherProps,
675
+ decoratorName,
661
676
  );
662
677
 
663
678
  fixes.push({
@@ -722,7 +737,7 @@ export default createRule<
722
737
  nullable = true;
723
738
  }
724
739
  } else {
725
- // 保留其他所有属性
740
+ // Keep all other properties
726
741
  otherProps.push({
727
742
  key: prop.key.name,
728
743
  value: source.getText(prop.value),
@@ -775,23 +790,23 @@ export default createRule<
775
790
  return { items, nullable };
776
791
  };
777
792
 
778
- // 检查文件中是否使用了 Opt 类型但没有导入
793
+ // Check if the file uses the Opt type but hasn't imported it
779
794
  const checkOptUsageWithoutImport = (node: TSESTree.ClassDeclaration) => {
780
795
  let usesOpt = false;
781
796
 
782
- // 遍历所有成员,检查是否有使用 Opt<T> 的类型注解
797
+ // Iterate through all members to check for Opt<T> type annotations
783
798
  node.body.body.forEach((member: TSESTree.ClassElement) => {
784
799
  if (member.type !== AST_NODE_TYPES.PropertyDefinition) return;
785
800
 
786
801
  const typeAnnotation = member.typeAnnotation?.typeAnnotation;
787
802
  if (!typeAnnotation) return;
788
803
 
789
- // 递归检查类型节点中是否包含 Opt
804
+ // Recursively check if the type node contains Opt
790
805
  const containsOpt = (typeNode: TSESTree.TypeNode): boolean => {
791
806
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
792
807
  if (!typeNode) return false;
793
808
 
794
- // 检查是否是 Opt<T>
809
+ // Check if it's Opt<T>
795
810
  if (
796
811
  typeNode.type === AST_NODE_TYPES.TSTypeReference &&
797
812
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -801,14 +816,14 @@ export default createRule<
801
816
  return true;
802
817
  }
803
818
 
804
- // 递归检查联合类型
819
+ // Recursively check union types
805
820
  if (typeNode.type === AST_NODE_TYPES.TSUnionType) {
806
821
  return typeNode.types.some((t: TSESTree.TypeNode) =>
807
822
  containsOpt(t),
808
823
  );
809
824
  }
810
825
 
811
- // 递归检查类型参数
826
+ // Recursively check type parameters
812
827
  if (
813
828
  typeNode.type === AST_NODE_TYPES.TSTypeReference &&
814
829
  typeNode.typeArguments?.params
@@ -818,7 +833,7 @@ export default createRule<
818
833
  );
819
834
  }
820
835
 
821
- // 递归检查数组元素类型
836
+ // Recursively check array element type
822
837
  if (typeNode.type === AST_NODE_TYPES.TSArrayType) {
823
838
  return containsOpt(typeNode.elementType);
824
839
  }
@@ -831,7 +846,7 @@ export default createRule<
831
846
  }
832
847
  });
833
848
 
834
- // 如果使用了 Opt 但没有导入,报告错误
849
+ // If Opt is used but not imported, report an error
835
850
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
836
851
  if (usesOpt && !hasOptImport()) {
837
852
  context.report({
@@ -849,13 +864,13 @@ export default createRule<
849
864
  ClassDeclaration(node) {
850
865
  if (!isEntityClass(node)) return;
851
866
 
852
- // 首先检查是否使用了 Opt 但没有导入
867
+ // First check if Opt is used but not imported
853
868
  checkOptUsageWithoutImport(node);
854
869
 
855
870
  node.body.body.forEach((member: TSESTree.ClassElement) => {
856
871
  if (member.type !== AST_NODE_TYPES.PropertyDefinition) return;
857
872
 
858
- // 检查关系装饰器(PrimaryKey, OneToOne, OneToMany, ManyToOne, ManyToMany
873
+ // Check relation decorators (OneToOne, OneToMany, ManyToOne, ManyToMany)
859
874
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
860
875
  const hasRelationDecorator = member.decorators?.some(
861
876
  (decorator: TSESTree.Decorator) => {
@@ -865,7 +880,6 @@ export default createRule<
865
880
  ) {
866
881
  const decoratorName = decorator.expression.callee.name;
867
882
  return [
868
- "PrimaryKey",
869
883
  "OneToOne",
870
884
  "OneToMany",
871
885
  "ManyToOne",
@@ -876,43 +890,74 @@ export default createRule<
876
890
  },
877
891
  );
878
892
 
879
- // 如果有关系装饰器,跳过检查(这些属性不需要 @Property 装饰器)
893
+ // If there are relation decorators, skip checking (these properties don't need @Property)
880
894
  if (hasRelationDecorator) return;
881
895
 
882
- // 检查 @Enum 装饰器
896
+ // Property-like decorators (decorators that need type checking)
897
+ const propertyLikeDecorators = [
898
+ "Property",
899
+ "PrimaryKey",
900
+ "EncryptedProperty",
901
+ "HashedProperty",
902
+ ];
903
+
904
+ // Find property-like decorator
883
905
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
884
- const enumDecorator = member.decorators?.find(
906
+ const propertyLikeDecorator = member.decorators?.find(
885
907
  (decorator: TSESTree.Decorator) => {
886
- return (
908
+ if (
887
909
  decorator.expression.type === AST_NODE_TYPES.CallExpression &&
888
- decorator.expression.callee.type ===
889
- AST_NODE_TYPES.Identifier &&
890
- decorator.expression.callee.name === "Enum"
891
- );
910
+ decorator.expression.callee.type === AST_NODE_TYPES.Identifier
911
+ ) {
912
+ return propertyLikeDecorators.includes(
913
+ decorator.expression.callee.name,
914
+ );
915
+ }
916
+ return false;
892
917
  },
893
918
  );
894
919
 
920
+ // Get decorator name
921
+ const getDecoratorName = (
922
+ decorator: TSESTree.Decorator,
923
+ ): string | null => {
924
+ if (
925
+ decorator.expression.type === AST_NODE_TYPES.CallExpression &&
926
+ decorator.expression.callee.type === AST_NODE_TYPES.Identifier
927
+ ) {
928
+ return decorator.expression.callee.name;
929
+ }
930
+ return null;
931
+ };
932
+
933
+ // Check @Enum decorator
895
934
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
896
- const propertyDecorator = member.decorators?.find(
935
+ const enumDecorator = member.decorators?.find(
897
936
  (decorator: TSESTree.Decorator) => {
898
937
  return (
899
938
  decorator.expression.type === AST_NODE_TYPES.CallExpression &&
900
939
  decorator.expression.callee.type ===
901
940
  AST_NODE_TYPES.Identifier &&
902
- decorator.expression.callee.name === "Property"
941
+ decorator.expression.callee.name === "Enum"
903
942
  );
904
943
  },
905
944
  );
906
945
 
946
+ // Use property-like decorator
947
+ const propertyDecorator = propertyLikeDecorator;
948
+ const currentDecoratorName = propertyDecorator
949
+ ? getDecoratorName(propertyDecorator)
950
+ : null;
951
+
907
952
  const typeInfo = computeTypeInfo(member);
908
953
 
909
954
  if (!typeInfo?.typeName) return;
910
955
 
911
- // 检查初始化值和 Opt<T> 类型的匹配
956
+ // Check initializer and Opt<T> type match
912
957
  const hasInitializer = member.value !== null;
913
958
  const isOptWrapped = isWrappedWithOpt(member);
914
959
 
915
- // 检查初始化值是否为 null
960
+ // Check if the initializer is null
916
961
 
917
962
  const isInitializedToNull =
918
963
  hasInitializer &&
@@ -920,14 +965,14 @@ export default createRule<
920
965
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
921
966
  member.value?.value === null;
922
967
 
923
- // 情况 1:有非 null 初始化值但没有使用 Opt<T>
968
+ // Case 1: Has non-null initializer but not wrapped with Opt<T>
924
969
  if (
925
970
  hasInitializer &&
926
971
  !isInitializedToNull &&
927
972
  !isOptWrapped &&
928
973
  !member.optional
929
974
  ) {
930
- // 有初始化值但没有使用 Opt<T> 包装,需要报错
975
+ // Has initializer but not wrapped with Opt<T>, needs to report error
931
976
  const typeAnnotation = member.typeAnnotation?.typeAnnotation;
932
977
  const needsImport = !hasOptImport();
933
978
 
@@ -943,7 +988,7 @@ export default createRule<
943
988
  fixer.replaceText(typeAnnotation, wrappedType),
944
989
  ];
945
990
 
946
- // 如果需要,添加 Opt 导入
991
+ // If needed, add Opt import
947
992
  if (needsImport) {
948
993
  const importFix = addOptImport(fixer);
949
994
  if (importFix) fixes.push(importFix);
@@ -953,7 +998,7 @@ export default createRule<
953
998
  },
954
999
  });
955
1000
  } else if (member.value) {
956
- // 没有类型注解,从初始化值推断类型
1001
+ // No type annotation, infer type from initializer
957
1002
  let inferredType: string | null = null;
958
1003
 
959
1004
  if (member.value.type === AST_NODE_TYPES.Literal) {
@@ -962,10 +1007,10 @@ export default createRule<
962
1007
  else if (valueType === "number") inferredType = "number";
963
1008
  else if (valueType === "string") inferredType = "string";
964
1009
  } else if (member.value.type === AST_NODE_TYPES.ArrayExpression) {
965
- // 空数组 [] 的情况,需要从 @Property 装饰器推断类型
1010
+ // Empty array [] case, need to infer type from @Property decorator
966
1011
  inferredType = "unknown[]";
967
1012
  } else if (member.value.type === AST_NODE_TYPES.NewExpression) {
968
- // new Date() 的情况
1013
+ // new Date() case
969
1014
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
970
1015
  if (member.value.callee?.type === AST_NODE_TYPES.Identifier) {
971
1016
  inferredType = member.value.callee.name;
@@ -984,7 +1029,7 @@ export default createRule<
984
1029
  fixer.insertTextAfter(propertyName, `: ${wrappedType}`),
985
1030
  ];
986
1031
 
987
- // 如果需要,添加 Opt 导入
1032
+ // If needed, add Opt import
988
1033
  if (needsImport) {
989
1034
  const importFix = addOptImport(fixer);
990
1035
  if (importFix) fixes.push(importFix);
@@ -997,11 +1042,11 @@ export default createRule<
997
1042
  }
998
1043
  }
999
1044
 
1000
- // 如果有 @Enum 装饰器,无论是否识别为枚举类型,都检查其配置
1045
+ // If there is an @Enum decorator, check its configuration regardless of whether the type is recognized as enum
1001
1046
  if (enumDecorator) {
1002
1047
  const enumConfig = parseEnumDecorator(enumDecorator);
1003
1048
 
1004
- // 检查 nullable 配置是否与 TypeScript 类型匹配
1049
+ // Check if nullable configuration matches the TypeScript type
1005
1050
  if (enumConfig.nullable !== typeInfo.isNullable) {
1006
1051
  const expectedEnumText = buildEnumDecorator(typeInfo);
1007
1052
  context.report({
@@ -1015,13 +1060,13 @@ export default createRule<
1015
1060
  },
1016
1061
  });
1017
1062
  }
1018
- // @Enum 装饰器的属性,不需要 @Property 装饰器
1063
+ // Properties with @Enum decorator do not need @Property decorator
1019
1064
  return;
1020
1065
  }
1021
1066
 
1022
- // 如果是枚举类型但没有 @Enum 装饰器
1067
+ // If it's an enum type but doesn't have @Enum decorator
1023
1068
  if (typeInfo.isEnum) {
1024
- // 如果有 @Property 装饰器,建议替换为 @Enum
1069
+ // If it has @Property decorator, suggest replacing with @Enum
1025
1070
  if (propertyDecorator) {
1026
1071
  context.report({
1027
1072
  node: propertyDecorator,
@@ -1037,7 +1082,7 @@ export default createRule<
1037
1082
  return;
1038
1083
  }
1039
1084
 
1040
- // 如果没有 @Enum 装饰器,添加它
1085
+ // If it doesn't have @Enum decorator, add one
1041
1086
  context.report({
1042
1087
  node: member,
1043
1088
  messageId: "useEnumDecorator",
@@ -1052,7 +1097,7 @@ export default createRule<
1052
1097
  return;
1053
1098
  }
1054
1099
 
1055
- // 非枚举类型:如果没有 @Property 装饰器,添加它
1100
+ // Non-enum type: if no @Property decorator, add one
1056
1101
  if (!propertyDecorator) {
1057
1102
  context.report({
1058
1103
  node: member,
@@ -1065,40 +1110,48 @@ export default createRule<
1065
1110
  return;
1066
1111
  }
1067
1112
 
1068
- // 检查现有 @Property 装饰器是否与类型匹配
1113
+ // Check if existing decorator matches the type
1069
1114
  const currentConfig = parsePropertyDecorator(propertyDecorator);
1070
- const expectedType = typeInfo.propertyType;
1115
+ let expectedType = typeInfo.propertyType;
1116
+
1117
+ // PrimaryKey number type expects t.integer
1118
+ if (
1119
+ currentDecoratorName === "PrimaryKey" &&
1120
+ expectedType === "t.float"
1121
+ ) {
1122
+ expectedType = "t.integer";
1123
+ }
1071
1124
 
1072
1125
  let needReport = false;
1073
1126
 
1074
- // 检查 type 配置
1127
+ // Check type configuration
1075
1128
  if (expectedType && currentConfig.type !== expectedType) {
1076
- // 对于 string 类型,接受多种有效配置
1129
+ // For string type, accept multiple valid configurations
1077
1130
  if (
1078
1131
  expectedType === "t.string" &&
1079
1132
  isValidStringType(currentConfig.type)
1080
1133
  ) {
1081
- // 当前配置是有效的 string 类型配置,不需要修改
1134
+ // Current config is a valid string type configuration, no modification needed
1082
1135
  } else if (
1083
- expectedType === "t.float" &&
1136
+ (expectedType === "t.float" || expectedType === "t.integer") &&
1084
1137
  isValidNumberType(currentConfig.type)
1085
1138
  ) {
1086
- // 当前配置是有效的 number 类型配置,不需要修改
1139
+ // Current config is a valid number type configuration, no modification needed
1087
1140
  } else if (
1088
1141
  expectedType === "t.datetime" &&
1089
1142
  isValidDateType(currentConfig.type)
1090
1143
  ) {
1091
- // 当前配置是有效的 Date 类型配置,不需要修改
1144
+ // Current config is a valid Date type configuration, no modification needed
1092
1145
  } else if (
1093
1146
  expectedType === "t.array" &&
1094
1147
  typeInfo.arrayElementTypeName === "number" &&
1095
1148
  isValidNumberArrayType(currentConfig.type)
1096
1149
  ) {
1097
- // 当前配置是有效的 number[] 类型配置(t.array VectorType),不需要修改
1150
+ // Current config is a valid number[] type configuration (t.array or VectorType), no modification needed
1098
1151
  } else if (expectedType === "t.json") {
1099
- // 对于 t.json 类型(Record 或自定义类型数组)
1152
+ // For t.json type (Record or custom type arrays)
1100
1153
  if (currentConfig.type === "t.json") {
1101
- // t.json 配置正确,不需要修改
1154
+ // t.json config is correct, no modification needed
1102
1155
  } else {
1103
1156
  needReport = true;
1104
1157
  }
@@ -1106,11 +1159,11 @@ export default createRule<
1106
1159
  needReport = true;
1107
1160
  }
1108
1161
  } else if (!expectedType && currentConfig.type) {
1109
- // 如果不需要 type 但当前有 type,也需要修正
1162
+ // If type is not needed but currently has one, needs correction
1110
1163
  needReport = true;
1111
1164
  }
1112
1165
 
1113
- // 检查 nullable 配置
1166
+ // Check nullable configuration
1114
1167
  if (currentConfig.nullable !== typeInfo.isNullable) {
1115
1168
  needReport = true;
1116
1169
  }
@@ -1126,6 +1179,7 @@ export default createRule<
1126
1179
  propertyDecorator,
1127
1180
  typeInfo,
1128
1181
  currentConfig,
1182
+ currentDecoratorName ?? "Property",
1129
1183
  );
1130
1184
  return applyFixes(fixer, fixes);
1131
1185
  },