@nest-boot/eslint-plugin 7.0.0-beta.0 → 7.0.0-beta.2

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 (80) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +2 -14
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/rules/graphql/graphql-field-config-from-types.d.ts +6 -0
  7. package/dist/rules/graphql/graphql-field-config-from-types.js +417 -0
  8. package/dist/rules/graphql/graphql-field-config-from-types.js.map +1 -0
  9. package/dist/rules/graphql/graphql-field-definite-assignment.d.ts +2 -0
  10. package/dist/rules/graphql/graphql-field-definite-assignment.js +125 -0
  11. package/dist/rules/graphql/graphql-field-definite-assignment.js.map +1 -0
  12. package/dist/rules/import/import-bullmq.d.ts +2 -0
  13. package/dist/rules/import/import-bullmq.js +36 -0
  14. package/dist/rules/import/import-bullmq.js.map +1 -0
  15. package/dist/rules/import/import-graphql.d.ts +2 -0
  16. package/dist/rules/import/import-graphql.js +36 -0
  17. package/dist/rules/import/import-graphql.js.map +1 -0
  18. package/dist/rules/import/import-mikro-orm.d.ts +2 -0
  19. package/dist/rules/import/import-mikro-orm.js +36 -0
  20. package/dist/rules/import/import-mikro-orm.js.map +1 -0
  21. package/dist/rules/index.d.ts +9 -0
  22. package/dist/rules/index.js +16 -11
  23. package/dist/rules/index.js.map +1 -1
  24. package/dist/rules/mikro-orm/entity-field-definite-assignment.d.ts +2 -0
  25. package/dist/rules/mikro-orm/entity-field-definite-assignment.js +125 -0
  26. package/dist/rules/mikro-orm/entity-field-definite-assignment.js.map +1 -0
  27. package/dist/rules/mikro-orm/entity-property-config-from-types.d.ts +3 -0
  28. package/dist/rules/mikro-orm/entity-property-config-from-types.js +881 -0
  29. package/dist/rules/mikro-orm/entity-property-config-from-types.js.map +1 -0
  30. package/dist/tsconfig.build.tsbuildinfo +1 -0
  31. package/dist/utils/createRule.d.ts +2 -0
  32. package/dist/utils/createRule.js +1 -1
  33. package/dist/utils/createRule.js.map +1 -1
  34. package/dist/utils/decorators.d.ts +29 -0
  35. package/dist/utils/decorators.js +74 -0
  36. package/dist/utils/decorators.js.map +1 -0
  37. package/dist/utils/tester.d.ts +2 -0
  38. package/dist/utils/tester.js +27 -0
  39. package/dist/utils/tester.js.map +1 -0
  40. package/eslint.config.mjs +38 -2
  41. package/jest.config.ts +12 -0
  42. package/package.json +22 -14
  43. package/src/index.ts +1 -1
  44. package/src/rules/graphql/graphql-field-config-from-types.spec.ts +242 -0
  45. package/src/rules/graphql/graphql-field-config-from-types.ts +557 -0
  46. package/src/rules/graphql/graphql-field-definite-assignment.spec.ts +135 -0
  47. package/src/rules/graphql/graphql-field-definite-assignment.ts +147 -0
  48. package/src/rules/import/import-bullmq.spec.ts +69 -0
  49. package/src/rules/import/import-bullmq.ts +35 -0
  50. package/src/rules/import/import-graphql.spec.ts +65 -0
  51. package/src/rules/import/import-graphql.ts +36 -0
  52. package/src/rules/import/import-mikro-orm.spec.ts +65 -0
  53. package/src/rules/import/import-mikro-orm.ts +36 -0
  54. package/src/rules/index.ts +15 -13
  55. package/src/rules/mikro-orm/entity-field-definite-assignment.spec.ts +91 -0
  56. package/src/rules/mikro-orm/entity-field-definite-assignment.ts +141 -0
  57. package/src/rules/mikro-orm/entity-property-config-from-types.spec.ts +262 -0
  58. package/src/rules/mikro-orm/entity-property-config-from-types.ts +1111 -0
  59. package/src/utils/createRule.ts +3 -1
  60. package/src/utils/decorators.spec.ts +214 -0
  61. package/src/utils/decorators.ts +93 -0
  62. package/src/utils/tester.ts +22 -0
  63. package/tsconfig.build.json +5 -0
  64. package/tsconfig.json +6 -7
  65. package/dist/rules/entity-constructor.js +0 -78
  66. package/dist/rules/entity-constructor.js.map +0 -1
  67. package/dist/rules/entity-property-no-optional-or-non-null-assertion.js +0 -63
  68. package/dist/rules/entity-property-no-optional-or-non-null-assertion.js.map +0 -1
  69. package/dist/rules/entity-property-nullable.js +0 -81
  70. package/dist/rules/entity-property-nullable.js.map +0 -1
  71. package/dist/rules/graphql-field-arguments-match-property-type.js +0 -118
  72. package/dist/rules/graphql-field-arguments-match-property-type.js.map +0 -1
  73. package/dist/rules/graphql-resolver-method-return-type.js +0 -145
  74. package/dist/rules/graphql-resolver-method-return-type.js.map +0 -1
  75. package/dist/tsconfig.tsbuildinfo +0 -1
  76. package/src/rules/entity-constructor.ts +0 -97
  77. package/src/rules/entity-property-no-optional-or-non-null-assertion.ts +0 -81
  78. package/src/rules/entity-property-nullable.ts +0 -112
  79. package/src/rules/graphql-field-arguments-match-property-type.ts +0 -186
  80. package/src/rules/graphql-resolver-method-return-type.ts +0 -207
@@ -0,0 +1,881 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const utils_1 = require("@typescript-eslint/utils");
37
+ const ts = __importStar(require("typescript"));
38
+ const createRule_1 = require("../../utils/createRule");
39
+ const decorators_1 = require("../../utils/decorators");
40
+ exports.default = (0, createRule_1.createRule)({
41
+ name: "entity-property-config-from-types",
42
+ meta: {
43
+ type: "problem",
44
+ docs: {
45
+ description: "根据 TypeScript 类型自动生成或修正 @Property 装饰器的类型与 nullable 配置(支持数组),以及 @Enum 装饰器的 nullable 配置。检查有初始化值的属性是否使用 Opt<T> 类型。",
46
+ },
47
+ fixable: "code",
48
+ schema: [],
49
+ messages: {
50
+ alignPropertyDecoratorWithTsType: "@Property 装饰器应与 TypeScript 类型保持一致(类型与 nullable)。",
51
+ removePropertyDecorator: "属性带有 @{{decoratorName}} 装饰器,应移除 @Property 装饰器。",
52
+ useEnumDecorator: "枚举类型应使用 @Enum 装饰器而不是 @Property 装饰器。",
53
+ useOptTypeForInitializedProperty: "有非 null 初始化值的属性应使用 Opt<T> 类型包装。",
54
+ removeOptTypeForNonInitializedProperty: "没有初始化值或初始化为 null 的属性不应使用 Opt<T> 类型包装。",
55
+ },
56
+ },
57
+ defaultOptions: [],
58
+ create(context) {
59
+ const source = context.sourceCode;
60
+ const parserServices = utils_1.ESLintUtils.getParserServices(context);
61
+ const checker = parserServices.program.getTypeChecker();
62
+ // 检查是否为枚举类型
63
+ const isEnumType = (node) => {
64
+ try {
65
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
66
+ const type = checker.getTypeAtLocation(tsNode);
67
+ // 检查是否是枚举类型
68
+ if (type.symbol.flags & ts.SymbolFlags.Enum) {
69
+ return true;
70
+ }
71
+ // 检查联合类型中的每个成员
72
+ if (type.isUnion()) {
73
+ return type.types.some((t) => t.symbol.flags & ts.SymbolFlags.EnumMember ||
74
+ t.symbol.flags & ts.SymbolFlags.Enum);
75
+ }
76
+ return false;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ };
82
+ const getPropertyType = (typeName) => {
83
+ if (!typeName)
84
+ return null;
85
+ switch (typeName) {
86
+ case "String":
87
+ case "string":
88
+ return "t.string"; // 默认使用 t.string
89
+ case "Number":
90
+ case "number":
91
+ return "t.float"; // 默认使用 t.float
92
+ case "Boolean":
93
+ case "boolean":
94
+ return "t.boolean"; // 默认使用 t.boolean
95
+ case "Date":
96
+ return null; // Date 类型不需要指定
97
+ case "GraphQLJSONObject":
98
+ case "Record":
99
+ return "t.json";
100
+ default:
101
+ return null;
102
+ }
103
+ };
104
+ const isValidStringType = (typeConfig) => {
105
+ if (!typeConfig)
106
+ return false;
107
+ // 接受 t.string, t.text, t.decimal, t.bigint
108
+ if (typeConfig === "t.string" ||
109
+ typeConfig === "t.text" ||
110
+ typeConfig === "t.decimal" ||
111
+ typeConfig === "t.bigint") {
112
+ return true;
113
+ }
114
+ // 接受 DecimalType(类引用,不带参数)
115
+ if (typeConfig === "DecimalType") {
116
+ return true;
117
+ }
118
+ // 接受 new DecimalType('string') 和 new BigIntType('string')
119
+ // 但排除 new BigIntType('number')
120
+ if (typeConfig.includes("DecimalType") ||
121
+ typeConfig.includes("BigIntType")) {
122
+ // 如果是 new BigIntType('number'),则无效
123
+ if (typeConfig.includes("BigIntType") &&
124
+ (typeConfig.includes("'number'") || typeConfig.includes('"number"'))) {
125
+ return false;
126
+ }
127
+ // 其他情况(包括 new XXXType('string') 和 DecimalType)都有效
128
+ return true;
129
+ }
130
+ return false;
131
+ };
132
+ const isValidNumberType = (typeConfig) => {
133
+ if (!typeConfig)
134
+ return false;
135
+ // 接受 t.integer, t.float, t.double, t.decimal
136
+ if (typeConfig === "t.integer" ||
137
+ typeConfig === "t.float" ||
138
+ typeConfig === "t.double" ||
139
+ typeConfig === "t.decimal") {
140
+ return true;
141
+ }
142
+ // 接受 BigIntType 和 DecimalType(类引用,不带参数)
143
+ if (typeConfig === "BigIntType" || typeConfig === "DecimalType") {
144
+ return true;
145
+ }
146
+ // 接受 new DecimalType('number') 和 new BigIntType('number')
147
+ // 但排除 new XXXType('string')
148
+ if (typeConfig.includes("DecimalType") ||
149
+ typeConfig.includes("BigIntType")) {
150
+ // 如果包含 'string',则对 number 类型无效
151
+ if (typeConfig.includes("'string'") ||
152
+ typeConfig.includes('"string"')) {
153
+ return false;
154
+ }
155
+ // 其他情况(包括 new XXXType('number'))都有效
156
+ return true;
157
+ }
158
+ return false;
159
+ };
160
+ const isValidNumberArrayType = (typeConfig) => {
161
+ if (!typeConfig)
162
+ return false;
163
+ // 接受 t.array(默认)
164
+ if (typeConfig === "t.array") {
165
+ return true;
166
+ }
167
+ // 接受 VectorType(类引用)
168
+ if (typeConfig === "VectorType") {
169
+ return true;
170
+ }
171
+ // 接受 new VectorType(...)
172
+ if (typeConfig.includes("VectorType")) {
173
+ return true;
174
+ }
175
+ return false;
176
+ };
177
+ const getIdentifierName = (node) => {
178
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
179
+ node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
180
+ return node.typeName.name;
181
+ }
182
+ return null;
183
+ };
184
+ const extractArrayElementType = (node) => {
185
+ if (node.type === utils_1.AST_NODE_TYPES.TSArrayType) {
186
+ return node.elementType;
187
+ }
188
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
189
+ node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
190
+ node.typeName.name === "Array") {
191
+ return node.typeArguments?.params[0] ?? null;
192
+ }
193
+ return null;
194
+ };
195
+ const isCollectionType = (node) => {
196
+ return (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
197
+ node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
198
+ node.typeName.name === "Collection");
199
+ };
200
+ // 检查类型是否被 Opt<T> 包装
201
+ const isWrappedWithOpt = (property) => {
202
+ const typeAnnotation = property.typeAnnotation;
203
+ if (typeAnnotation?.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
204
+ return false;
205
+ }
206
+ const typeNode = typeAnnotation.typeAnnotation;
207
+ return (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
208
+ typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
209
+ typeNode.typeName.name === "Opt");
210
+ };
211
+ // 检查是否已导入 Opt
212
+ const hasOptImport = () => {
213
+ const program = context.sourceCode.ast;
214
+ for (const statement of program.body) {
215
+ if (statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
216
+ const importSource = statement.source.value;
217
+ // 检查从 @mikro-orm/* 导入的语句
218
+ if (typeof importSource === "string" &&
219
+ importSource.startsWith("@mikro-orm/")) {
220
+ const hasOpt = statement.specifiers.some((spec) => {
221
+ return (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
222
+ spec.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
223
+ spec.imported.name === "Opt");
224
+ });
225
+ if (hasOpt)
226
+ return true;
227
+ }
228
+ }
229
+ }
230
+ return false;
231
+ };
232
+ // 添加 Opt 到 @mikro-orm/core 的导入
233
+ const addOptImport = (fixer) => {
234
+ const program = context.sourceCode.ast;
235
+ // 查找 @mikro-orm/core 的导入语句
236
+ let coreImport = null;
237
+ for (const statement of program.body) {
238
+ if (statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
239
+ const importSource = statement.source.value;
240
+ if (importSource === "@mikro-orm/core") {
241
+ coreImport = statement;
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ if (coreImport) {
247
+ // 已有 @mikro-orm/core 导入,添加 Opt 到导入列表
248
+ const lastSpecifier = coreImport.specifiers[coreImport.specifiers.length - 1];
249
+ // 检查是否是多行导入
250
+ const importText = source.getText(coreImport);
251
+ const isMultiline = importText.includes("\n");
252
+ if (isMultiline) {
253
+ // 多行导入:在最后一个导入项后添加,保持缩进
254
+ const indent = " "; // 假设使用 2 个空格缩进
255
+ return fixer.insertTextAfter(lastSpecifier, `,\n${indent}Opt`);
256
+ }
257
+ else {
258
+ // 单行导入:直接添加
259
+ return fixer.insertTextAfter(lastSpecifier, ", Opt");
260
+ }
261
+ }
262
+ else {
263
+ // 没有 @mikro-orm/core 导入,在文件开头添加新的导入语句
264
+ const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
265
+ if (firstImport) {
266
+ return fixer.insertTextBefore(firstImport, "import { Opt } from '@mikro-orm/core';\n");
267
+ }
268
+ }
269
+ return null;
270
+ };
271
+ const computeTypeInfo = (property) => {
272
+ let isNullable = false;
273
+ let isArray = false;
274
+ let isEnum = false;
275
+ let baseTypeNode = property.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation
276
+ ? property.typeAnnotation.typeAnnotation
277
+ : null;
278
+ // 可选属性(?)视为可空
279
+ if (property.optional) {
280
+ isNullable = true;
281
+ }
282
+ // 处理联合类型中的 null/undefined
283
+ if (baseTypeNode?.type === utils_1.AST_NODE_TYPES.TSUnionType) {
284
+ const hasNullish = baseTypeNode.types.some((t) => {
285
+ return (t.type === utils_1.AST_NODE_TYPES.TSNullKeyword ||
286
+ t.type === utils_1.AST_NODE_TYPES.TSUndefinedKeyword);
287
+ });
288
+ if (hasNullish)
289
+ isNullable = true;
290
+ baseTypeNode =
291
+ baseTypeNode.types.find((t) => {
292
+ return (t.type !== utils_1.AST_NODE_TYPES.TSNullKeyword &&
293
+ t.type !== utils_1.AST_NODE_TYPES.TSUndefinedKeyword);
294
+ }) ?? null;
295
+ }
296
+ // 先解包 Ref<T> 和 Opt<T> → T,并在内部再次处理 null/undefined
297
+ if (baseTypeNode?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
298
+ baseTypeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
299
+ (baseTypeNode.typeName.name === "Ref" ||
300
+ baseTypeNode.typeName.name === "Opt")) {
301
+ let inner = baseTypeNode.typeArguments?.params[0] ?? null;
302
+ if (inner && inner.type === utils_1.AST_NODE_TYPES.TSUnionType) {
303
+ const hasNullish = inner.types.some((t) => {
304
+ return (t.type === utils_1.AST_NODE_TYPES.TSNullKeyword ||
305
+ t.type === utils_1.AST_NODE_TYPES.TSUndefinedKeyword);
306
+ });
307
+ if (hasNullish)
308
+ isNullable = true;
309
+ inner =
310
+ inner.types.find((t) => {
311
+ return (t.type !== utils_1.AST_NODE_TYPES.TSNullKeyword &&
312
+ t.type !== utils_1.AST_NODE_TYPES.TSUndefinedKeyword);
313
+ }) ?? inner;
314
+ }
315
+ baseTypeNode = inner ?? baseTypeNode;
316
+ }
317
+ // 跳过 Collection<T> 类型(这些应该由 OneToMany 等装饰器处理)
318
+ if (baseTypeNode && isCollectionType(baseTypeNode)) {
319
+ return null;
320
+ }
321
+ // 数组类型(T[] 或 Array<T>)- 在解包 Opt/Ref 之后检查
322
+ const elementTypeNode = baseTypeNode
323
+ ? extractArrayElementType(baseTypeNode)
324
+ : null;
325
+ if (elementTypeNode) {
326
+ isArray = true;
327
+ }
328
+ const targetTypeNode = elementTypeNode ?? baseTypeNode;
329
+ // 检查目标类型是否为枚举
330
+ if (targetTypeNode) {
331
+ isEnum = isEnumType(targetTypeNode);
332
+ }
333
+ // 无显式类型时,尝试从字面量初始值推断
334
+ if (!targetTypeNode) {
335
+ if (property.value?.type === utils_1.AST_NODE_TYPES.Literal) {
336
+ const value = property.value.value;
337
+ const typeOf = typeof value;
338
+ if (typeOf === "boolean")
339
+ return {
340
+ typeName: "boolean",
341
+ isArray,
342
+ isNullable,
343
+ isEnum: false,
344
+ propertyType: isArray ? "t.json" : getPropertyType("boolean"),
345
+ arrayElementTypeName: isArray ? "boolean" : undefined,
346
+ };
347
+ if (typeOf === "number")
348
+ return {
349
+ typeName: "number",
350
+ isArray,
351
+ isNullable,
352
+ isEnum: false,
353
+ propertyType: isArray ? "t.array" : getPropertyType("number"),
354
+ arrayElementTypeName: isArray ? "number" : undefined,
355
+ };
356
+ if (typeOf === "string")
357
+ return {
358
+ typeName: "string",
359
+ isArray,
360
+ isNullable,
361
+ isEnum: false,
362
+ propertyType: isArray ? "t.array" : getPropertyType("string"),
363
+ arrayElementTypeName: isArray ? "string" : undefined,
364
+ };
365
+ }
366
+ return null;
367
+ }
368
+ // Record<*, *> → t.json
369
+ if (targetTypeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
370
+ targetTypeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
371
+ targetTypeNode.typeName.name === "Record") {
372
+ return {
373
+ typeName: "Record",
374
+ isArray,
375
+ isNullable,
376
+ isEnum: false,
377
+ propertyType: "t.json",
378
+ arrayElementTypeName: isArray ? "Record" : undefined,
379
+ };
380
+ }
381
+ // 关键字类型
382
+ if (targetTypeNode.type === utils_1.AST_NODE_TYPES.TSStringKeyword) {
383
+ return {
384
+ typeName: "string",
385
+ isArray,
386
+ isNullable,
387
+ isEnum: false,
388
+ propertyType: isArray ? "t.array" : getPropertyType("string"),
389
+ arrayElementTypeName: isArray ? "string" : undefined,
390
+ };
391
+ }
392
+ if (targetTypeNode.type === utils_1.AST_NODE_TYPES.TSNumberKeyword) {
393
+ return {
394
+ typeName: "number",
395
+ isArray,
396
+ isNullable,
397
+ isEnum: false,
398
+ propertyType: isArray ? "t.array" : getPropertyType("number"),
399
+ arrayElementTypeName: isArray ? "number" : undefined,
400
+ };
401
+ }
402
+ if (targetTypeNode.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
403
+ return {
404
+ typeName: "boolean",
405
+ isArray,
406
+ isNullable,
407
+ isEnum: false,
408
+ propertyType: isArray ? "t.json" : getPropertyType("boolean"),
409
+ arrayElementTypeName: isArray ? "boolean" : undefined,
410
+ };
411
+ }
412
+ // 标识符(类/自定义类型)
413
+ const ident = getIdentifierName(targetTypeNode);
414
+ if (ident) {
415
+ // 如果是数组,自定义类型数组使用 t.json
416
+ if (isArray) {
417
+ return {
418
+ typeName: ident,
419
+ isArray,
420
+ isNullable,
421
+ isEnum,
422
+ propertyType: "t.json",
423
+ arrayElementTypeName: ident,
424
+ };
425
+ }
426
+ return {
427
+ typeName: ident,
428
+ isArray,
429
+ isNullable,
430
+ isEnum,
431
+ propertyType: getPropertyType(ident),
432
+ arrayElementTypeName: undefined,
433
+ };
434
+ }
435
+ return null;
436
+ };
437
+ // 将类型包装为 Opt<T>
438
+ const wrapWithOpt = (typeString) => {
439
+ return `Opt<${typeString}>`;
440
+ };
441
+ const buildEnumDecorator = (info) => {
442
+ const options = [];
443
+ if (info.typeName) {
444
+ options.push(`items: () => ${info.typeName}`);
445
+ }
446
+ if (info.isNullable) {
447
+ options.push("nullable: true");
448
+ }
449
+ if (options.length === 0) {
450
+ return "@Enum()";
451
+ }
452
+ return `@Enum({ ${options.join(", ")} })`;
453
+ };
454
+ const buildPropertyDecorator = (info, otherProps = []) => {
455
+ const options = [];
456
+ // 如果有 propertyType 配置,添加 type
457
+ if (info.propertyType) {
458
+ options.push(`type: ${info.propertyType}`);
459
+ }
460
+ // 添加其他属性(保持原有顺序)
461
+ for (const prop of otherProps) {
462
+ options.push(`${prop.key}: ${prop.value}`);
463
+ }
464
+ if (info.isNullable) {
465
+ options.push("nullable: true");
466
+ }
467
+ if (options.length === 0) {
468
+ return "@Property()";
469
+ }
470
+ return `@Property({ ${options.join(", ")} })`;
471
+ };
472
+ const addPropertyDecorator = (property, info) => {
473
+ const fixes = [];
474
+ const newDecoratorText = buildPropertyDecorator(info);
475
+ fixes.push({
476
+ type: "insert",
477
+ range: property.range,
478
+ text: newDecoratorText + "\n ",
479
+ });
480
+ return fixes;
481
+ };
482
+ const fixWithTypeInfo = (property, propertyDecorator, info, currentConfig) => {
483
+ const fixes = [];
484
+ // 如果当前已经有有效的配置,保留它而不是替换成默认值
485
+ const finalInfo = { ...info };
486
+ if (info.propertyType === "t.string" &&
487
+ isValidStringType(currentConfig.type)) {
488
+ // 保留有效的 string 类型配置
489
+ finalInfo.propertyType = currentConfig.type;
490
+ }
491
+ else if (info.propertyType === "t.float" &&
492
+ isValidNumberType(currentConfig.type)) {
493
+ // 保留有效的 number 类型配置
494
+ finalInfo.propertyType = currentConfig.type;
495
+ }
496
+ else if (info.isArray &&
497
+ info.arrayElementTypeName === "number" &&
498
+ isValidNumberArrayType(currentConfig.type)) {
499
+ // 保留有效的 number[] 类型配置(VectorType)
500
+ finalInfo.propertyType = currentConfig.type;
501
+ }
502
+ // 保留其他属性配置
503
+ const newDecoratorText = buildPropertyDecorator(finalInfo, currentConfig.otherProps);
504
+ fixes.push({
505
+ type: "replace",
506
+ range: propertyDecorator.range,
507
+ text: newDecoratorText,
508
+ });
509
+ return fixes;
510
+ };
511
+ const applyFixes = (fixer, fixes) => {
512
+ return fixes.map((fix) => {
513
+ if (fix.type === "replace") {
514
+ return fixer.replaceTextRange(fix.range, fix.text);
515
+ }
516
+ else {
517
+ return fixer.insertTextBeforeRange(fix.range, fix.text);
518
+ }
519
+ });
520
+ };
521
+ const isEntityClass = (node) => {
522
+ return (0, decorators_1.hasClassDecorator)(node, "Entity");
523
+ };
524
+ const parsePropertyDecorator = (decorator) => {
525
+ const callExpr = decorator.expression;
526
+ if (callExpr.type !== utils_1.AST_NODE_TYPES.CallExpression ||
527
+ callExpr.arguments.length === 0) {
528
+ return { type: null, nullable: false, otherProps: [] };
529
+ }
530
+ const firstArg = callExpr.arguments[0];
531
+ if (firstArg.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
532
+ return { type: null, nullable: false, otherProps: [] };
533
+ }
534
+ let type = null;
535
+ let nullable = false;
536
+ const otherProps = [];
537
+ for (const prop of firstArg.properties) {
538
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
539
+ continue;
540
+ if (prop.key.type !== utils_1.AST_NODE_TYPES.Identifier || !prop.key.name)
541
+ continue;
542
+ if (prop.key.name === "type") {
543
+ type = source.getText(prop.value);
544
+ }
545
+ else if (prop.key.name === "nullable") {
546
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
547
+ prop.value.value === true) {
548
+ nullable = true;
549
+ }
550
+ }
551
+ else {
552
+ // 保留其他所有属性
553
+ otherProps.push({
554
+ key: prop.key.name,
555
+ value: source.getText(prop.value),
556
+ });
557
+ }
558
+ }
559
+ return { type, nullable, otherProps };
560
+ };
561
+ const parseEnumDecorator = (decorator) => {
562
+ const callExpr = decorator.expression;
563
+ if (callExpr.type !== utils_1.AST_NODE_TYPES.CallExpression ||
564
+ callExpr.arguments.length === 0) {
565
+ return { items: null, nullable: false };
566
+ }
567
+ const firstArg = callExpr.arguments[0];
568
+ if (firstArg.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
569
+ return { items: null, nullable: false };
570
+ }
571
+ let items = null;
572
+ let nullable = false;
573
+ for (const prop of firstArg.properties) {
574
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
575
+ continue;
576
+ if (prop.key.type !== utils_1.AST_NODE_TYPES.Identifier || !prop.key.name)
577
+ continue;
578
+ if (prop.key.name === "items") {
579
+ items = source.getText(prop.value);
580
+ }
581
+ else if (prop.key.name === "nullable") {
582
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
583
+ prop.value.value === true) {
584
+ nullable = true;
585
+ }
586
+ }
587
+ }
588
+ return { items, nullable };
589
+ };
590
+ // 检查文件中是否使用了 Opt 类型但没有导入
591
+ const checkOptUsageWithoutImport = (node) => {
592
+ let usesOpt = false;
593
+ // 遍历所有成员,检查是否有使用 Opt<T> 的类型注解
594
+ node.body.body.forEach((member) => {
595
+ if (member.type !== utils_1.AST_NODE_TYPES.PropertyDefinition)
596
+ return;
597
+ const typeAnnotation = member.typeAnnotation?.typeAnnotation;
598
+ if (!typeAnnotation)
599
+ return;
600
+ // 递归检查类型节点中是否包含 Opt
601
+ const containsOpt = (typeNode) => {
602
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
603
+ if (!typeNode)
604
+ return false;
605
+ // 检查是否是 Opt<T>
606
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
607
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
608
+ typeNode.typeName?.type === utils_1.AST_NODE_TYPES.Identifier &&
609
+ typeNode.typeName.name === "Opt") {
610
+ return true;
611
+ }
612
+ // 递归检查联合类型
613
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
614
+ return typeNode.types.some((t) => containsOpt(t));
615
+ }
616
+ // 递归检查类型参数
617
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
618
+ typeNode.typeArguments?.params) {
619
+ return typeNode.typeArguments.params.some((param) => containsOpt(param));
620
+ }
621
+ // 递归检查数组元素类型
622
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSArrayType) {
623
+ return containsOpt(typeNode.elementType);
624
+ }
625
+ return false;
626
+ };
627
+ if (containsOpt(typeAnnotation)) {
628
+ usesOpt = true;
629
+ }
630
+ });
631
+ // 如果使用了 Opt 但没有导入,报告错误
632
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
633
+ if (usesOpt && !hasOptImport()) {
634
+ context.report({
635
+ node,
636
+ messageId: "useOptTypeForInitializedProperty",
637
+ fix: (fixer) => {
638
+ const importFix = addOptImport(fixer);
639
+ return importFix ? [importFix] : [];
640
+ },
641
+ });
642
+ }
643
+ };
644
+ return {
645
+ ClassDeclaration(node) {
646
+ if (!isEntityClass(node))
647
+ return;
648
+ // 首先检查是否使用了 Opt 但没有导入
649
+ checkOptUsageWithoutImport(node);
650
+ node.body.body.forEach((member) => {
651
+ if (member.type !== utils_1.AST_NODE_TYPES.PropertyDefinition)
652
+ return;
653
+ // 检查关系装饰器(PrimaryKey, OneToOne, OneToMany, ManyToOne, ManyToMany)
654
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
655
+ const hasRelationDecorator = member.decorators?.some((decorator) => {
656
+ if (decorator.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
657
+ decorator.expression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
658
+ const decoratorName = decorator.expression.callee.name;
659
+ return [
660
+ "PrimaryKey",
661
+ "OneToOne",
662
+ "OneToMany",
663
+ "ManyToOne",
664
+ "ManyToMany",
665
+ ].includes(decoratorName);
666
+ }
667
+ return false;
668
+ });
669
+ // 如果有关系装饰器,跳过检查(这些属性不需要 @Property 装饰器)
670
+ if (hasRelationDecorator)
671
+ return;
672
+ // 检查 @Enum 装饰器
673
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
674
+ const enumDecorator = member.decorators?.find((decorator) => {
675
+ return (decorator.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
676
+ decorator.expression.callee.type ===
677
+ utils_1.AST_NODE_TYPES.Identifier &&
678
+ decorator.expression.callee.name === "Enum");
679
+ });
680
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
681
+ const propertyDecorator = member.decorators?.find((decorator) => {
682
+ return (decorator.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
683
+ decorator.expression.callee.type ===
684
+ utils_1.AST_NODE_TYPES.Identifier &&
685
+ decorator.expression.callee.name === "Property");
686
+ });
687
+ const typeInfo = computeTypeInfo(member);
688
+ if (!typeInfo?.typeName)
689
+ return;
690
+ // 检查初始化值和 Opt<T> 类型的匹配
691
+ const hasInitializer = member.value !== null;
692
+ const isOptWrapped = isWrappedWithOpt(member);
693
+ // 检查初始化值是否为 null
694
+ const isInitializedToNull = hasInitializer &&
695
+ member.value?.type === utils_1.AST_NODE_TYPES.Literal &&
696
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
697
+ member.value?.value === null;
698
+ // 情况 1:有非 null 初始化值但没有使用 Opt<T>
699
+ if (hasInitializer &&
700
+ !isInitializedToNull &&
701
+ !isOptWrapped &&
702
+ !member.optional) {
703
+ // 有初始化值但没有使用 Opt<T> 包装,需要报错
704
+ const typeAnnotation = member.typeAnnotation?.typeAnnotation;
705
+ const needsImport = !hasOptImport();
706
+ if (typeAnnotation) {
707
+ const currentTypeText = source.getText(typeAnnotation);
708
+ const wrappedType = wrapWithOpt(currentTypeText);
709
+ context.report({
710
+ node: member,
711
+ messageId: "useOptTypeForInitializedProperty",
712
+ fix: (fixer) => {
713
+ const fixes = [
714
+ fixer.replaceText(typeAnnotation, wrappedType),
715
+ ];
716
+ // 如果需要,添加 Opt 导入
717
+ if (needsImport) {
718
+ const importFix = addOptImport(fixer);
719
+ if (importFix)
720
+ fixes.push(importFix);
721
+ }
722
+ return fixes;
723
+ },
724
+ });
725
+ }
726
+ else if (member.value) {
727
+ // 没有类型注解,从初始化值推断类型
728
+ let inferredType = null;
729
+ if (member.value.type === utils_1.AST_NODE_TYPES.Literal) {
730
+ const valueType = typeof member.value.value;
731
+ if (valueType === "boolean")
732
+ inferredType = "boolean";
733
+ else if (valueType === "number")
734
+ inferredType = "number";
735
+ else if (valueType === "string")
736
+ inferredType = "string";
737
+ }
738
+ else if (member.value.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
739
+ // 空数组 [] 的情况,需要从 @Property 装饰器推断类型
740
+ inferredType = "unknown[]";
741
+ }
742
+ else if (member.value.type === utils_1.AST_NODE_TYPES.NewExpression) {
743
+ // new Date() 的情况
744
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
745
+ if (member.value.callee?.type === utils_1.AST_NODE_TYPES.Identifier) {
746
+ inferredType = member.value.callee.name;
747
+ }
748
+ }
749
+ if (inferredType) {
750
+ const wrappedType = wrapWithOpt(inferredType);
751
+ const propertyName = member.key;
752
+ context.report({
753
+ node: member,
754
+ messageId: "useOptTypeForInitializedProperty",
755
+ fix: (fixer) => {
756
+ const fixes = [
757
+ fixer.insertTextAfter(propertyName, `: ${wrappedType}`),
758
+ ];
759
+ // 如果需要,添加 Opt 导入
760
+ if (needsImport) {
761
+ const importFix = addOptImport(fixer);
762
+ if (importFix)
763
+ fixes.push(importFix);
764
+ }
765
+ return fixes;
766
+ },
767
+ });
768
+ }
769
+ }
770
+ }
771
+ // 如果有 @Enum 装饰器,无论是否识别为枚举类型,都检查其配置
772
+ if (enumDecorator) {
773
+ const enumConfig = parseEnumDecorator(enumDecorator);
774
+ // 检查 nullable 配置是否与 TypeScript 类型匹配
775
+ if (enumConfig.nullable !== typeInfo.isNullable) {
776
+ const expectedEnumText = buildEnumDecorator(typeInfo);
777
+ context.report({
778
+ node: enumDecorator,
779
+ messageId: "alignPropertyDecoratorWithTsType",
780
+ fix: (fixer) => {
781
+ return fixer.replaceTextRange(enumDecorator.range, expectedEnumText);
782
+ },
783
+ });
784
+ }
785
+ // 有 @Enum 装饰器的属性,不需要 @Property 装饰器
786
+ return;
787
+ }
788
+ // 如果是枚举类型但没有 @Enum 装饰器
789
+ if (typeInfo.isEnum) {
790
+ // 如果有 @Property 装饰器,建议替换为 @Enum
791
+ if (propertyDecorator) {
792
+ context.report({
793
+ node: propertyDecorator,
794
+ messageId: "useEnumDecorator",
795
+ fix: (fixer) => {
796
+ const newDecoratorText = buildEnumDecorator(typeInfo);
797
+ return fixer.replaceTextRange(propertyDecorator.range, newDecoratorText);
798
+ },
799
+ });
800
+ return;
801
+ }
802
+ // 如果没有 @Enum 装饰器,添加它
803
+ context.report({
804
+ node: member,
805
+ messageId: "useEnumDecorator",
806
+ fix: (fixer) => {
807
+ const newDecoratorText = buildEnumDecorator(typeInfo);
808
+ return fixer.insertTextBeforeRange(member.range, newDecoratorText + "\n ");
809
+ },
810
+ });
811
+ return;
812
+ }
813
+ // 非枚举类型:如果没有 @Property 装饰器,添加它
814
+ if (!propertyDecorator) {
815
+ context.report({
816
+ node: member,
817
+ messageId: "alignPropertyDecoratorWithTsType",
818
+ fix: (fixer) => {
819
+ const fixes = addPropertyDecorator(member, typeInfo);
820
+ return applyFixes(fixer, fixes);
821
+ },
822
+ });
823
+ return;
824
+ }
825
+ // 检查现有 @Property 装饰器是否与类型匹配
826
+ const currentConfig = parsePropertyDecorator(propertyDecorator);
827
+ const expectedType = typeInfo.propertyType;
828
+ let needReport = false;
829
+ // 检查 type 配置
830
+ if (expectedType && currentConfig.type !== expectedType) {
831
+ // 对于 string 类型,接受多种有效配置
832
+ if (expectedType === "t.string" &&
833
+ isValidStringType(currentConfig.type)) {
834
+ // 当前配置是有效的 string 类型配置,不需要修改
835
+ }
836
+ else if (expectedType === "t.float" &&
837
+ isValidNumberType(currentConfig.type)) {
838
+ // 当前配置是有效的 number 类型配置,不需要修改
839
+ }
840
+ else if (expectedType === "t.array" &&
841
+ typeInfo.arrayElementTypeName === "number" &&
842
+ isValidNumberArrayType(currentConfig.type)) {
843
+ // 当前配置是有效的 number[] 类型配置(t.array 或 VectorType),不需要修改
844
+ }
845
+ else if (expectedType === "t.json") {
846
+ // 对于 t.json 类型(Record 或自定义类型数组)
847
+ if (currentConfig.type === "t.json") {
848
+ // t.json 配置正确,不需要修改
849
+ }
850
+ else {
851
+ needReport = true;
852
+ }
853
+ }
854
+ else {
855
+ needReport = true;
856
+ }
857
+ }
858
+ else if (!expectedType && currentConfig.type) {
859
+ // 如果不需要 type 但当前有 type,也需要修正
860
+ needReport = true;
861
+ }
862
+ // 检查 nullable 配置
863
+ if (currentConfig.nullable !== typeInfo.isNullable) {
864
+ needReport = true;
865
+ }
866
+ if (!needReport)
867
+ return;
868
+ context.report({
869
+ node: member,
870
+ messageId: "alignPropertyDecoratorWithTsType",
871
+ fix: (fixer) => {
872
+ const fixes = fixWithTypeInfo(member, propertyDecorator, typeInfo, currentConfig);
873
+ return applyFixes(fixer, fixes);
874
+ },
875
+ });
876
+ });
877
+ },
878
+ };
879
+ },
880
+ });
881
+ //# sourceMappingURL=entity-property-config-from-types.js.map