@nest-boot/eslint-plugin 7.0.6 → 7.0.7

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.
@@ -274,8 +274,11 @@ export default createRule<
274
274
  );
275
275
  };
276
276
 
277
- // Check if Opt is imported
278
- const hasOptImport = (): boolean => {
277
+ // Check if a named MikroORM helper is imported
278
+ const hasMikroOrmImport = (
279
+ importName: string,
280
+ options: { valueOnly?: boolean } = {},
281
+ ): boolean => {
279
282
  const program = context.sourceCode.ast;
280
283
  for (const statement of program.body) {
281
284
  if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
@@ -285,25 +288,46 @@ export default createRule<
285
288
  typeof importSource === "string" &&
286
289
  importSource.startsWith("@mikro-orm/")
287
290
  ) {
288
- const hasOpt = statement.specifiers.some(
291
+ if (options.valueOnly && statement.importKind === "type") {
292
+ continue;
293
+ }
294
+
295
+ const hasImport = statement.specifiers.some(
289
296
  (spec: TSESTree.ImportClause) => {
290
297
  return (
291
298
  spec.type === AST_NODE_TYPES.ImportSpecifier &&
292
- spec.imported.type === AST_NODE_TYPES.Identifier &&
293
- spec.imported.name === "Opt"
299
+ (!options.valueOnly || spec.importKind !== "type") &&
300
+ spec.local.name === importName
294
301
  );
295
302
  },
296
303
  );
297
- if (hasOpt) return true;
304
+ if (hasImport) return true;
298
305
  }
299
306
  }
300
307
  }
301
308
  return false;
302
309
  };
303
310
 
304
- // Add Opt to the @mikro-orm/core import
305
- const addOptImport = (fixer: RuleFixer): RuleFix | null => {
311
+ const hasOptImport = (): boolean => hasMikroOrmImport("Opt");
312
+ const hasEnumImport = (): boolean =>
313
+ hasMikroOrmImport("Enum", { valueOnly: true });
314
+ const hasPropertyImport = (): boolean =>
315
+ hasMikroOrmImport("Property", { valueOnly: true });
316
+ const hasTImport = (): boolean =>
317
+ hasMikroOrmImport("t", { valueOnly: true });
318
+
319
+ // Add named helpers to the @mikro-orm/core import
320
+ const addMikroOrmImports = (
321
+ fixer: RuleFixer,
322
+ importNames: string[],
323
+ options: { valueImport?: boolean } = {},
324
+ ): RuleFix | null => {
306
325
  const program = context.sourceCode.ast;
326
+ const uniqueImportNames = [...new Set(importNames)];
327
+
328
+ if (uniqueImportNames.length === 0) {
329
+ return null;
330
+ }
307
331
 
308
332
  // Find the @mikro-orm/core import statement
309
333
  let coreImport: TSESTree.ImportDeclaration | null = null;
@@ -311,7 +335,10 @@ export default createRule<
311
335
  for (const statement of program.body) {
312
336
  if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
313
337
  const importSource = statement.source.value;
314
- if (importSource === "@mikro-orm/core") {
338
+ if (
339
+ importSource === "@mikro-orm/core" &&
340
+ (!options.valueImport || statement.importKind !== "type")
341
+ ) {
315
342
  coreImport = statement;
316
343
  break;
317
344
  }
@@ -319,7 +346,7 @@ export default createRule<
319
346
  }
320
347
 
321
348
  if (coreImport) {
322
- // Already has @mikro-orm/core import, add Opt to the import list
349
+ // Already has @mikro-orm/core import, add the helper to the import list
323
350
  const lastSpecifier =
324
351
  coreImport.specifiers[coreImport.specifiers.length - 1];
325
352
 
@@ -330,10 +357,16 @@ export default createRule<
330
357
  if (isMultiline) {
331
358
  // Multiline import: add after the last import item, keeping indentation
332
359
  const indent = " "; // Assuming 2-space indentation
333
- return fixer.insertTextAfter(lastSpecifier, `,\n${indent}Opt`);
360
+ return fixer.insertTextAfter(
361
+ lastSpecifier,
362
+ uniqueImportNames.map((name) => `,\n${indent}${name}`).join(""),
363
+ );
334
364
  } else {
335
365
  // Single-line import: add directly
336
- return fixer.insertTextAfter(lastSpecifier, ", Opt");
366
+ return fixer.insertTextAfter(
367
+ lastSpecifier,
368
+ `, ${uniqueImportNames.join(", ")}`,
369
+ );
337
370
  }
338
371
  } else {
339
372
  // No @mikro-orm/core import, add a new import statement at the top
@@ -345,13 +378,42 @@ export default createRule<
345
378
  if (firstImport) {
346
379
  return fixer.insertTextBefore(
347
380
  firstImport,
348
- "import { Opt } from '@mikro-orm/core';\n",
381
+ `import { ${uniqueImportNames.join(", ")} } from '@mikro-orm/core';\n`,
349
382
  );
350
383
  }
351
384
  }
352
385
  return null;
353
386
  };
354
387
 
388
+ const addMikroOrmImport = (
389
+ fixer: RuleFixer,
390
+ importName: string,
391
+ options: { valueImport?: boolean } = {},
392
+ ): RuleFix | null => addMikroOrmImports(fixer, [importName], options);
393
+
394
+ const addOptImport = (fixer: RuleFixer): RuleFix | null =>
395
+ addMikroOrmImport(fixer, "Opt");
396
+
397
+ const addEnumImport = (fixer: RuleFixer): RuleFix | null =>
398
+ addMikroOrmImport(fixer, "Enum", { valueImport: true });
399
+
400
+ const addPropertyDecoratorImports = (
401
+ fixer: RuleFixer,
402
+ info: TypeInfo,
403
+ ): RuleFix | null => {
404
+ const importNames: string[] = [];
405
+
406
+ if (!hasPropertyImport()) {
407
+ importNames.push("Property");
408
+ }
409
+
410
+ if (info.propertyType?.startsWith("t.") && !hasTImport()) {
411
+ importNames.push("t");
412
+ }
413
+
414
+ return addMikroOrmImports(fixer, importNames, { valueImport: true });
415
+ };
416
+
355
417
  const computeTypeInfo = (
356
418
  property: TSESTree.PropertyDefinition,
357
419
  ): TypeInfo | null => {
@@ -1073,10 +1135,15 @@ export default createRule<
1073
1135
  messageId: "useEnumDecorator",
1074
1136
  fix: (fixer) => {
1075
1137
  const newDecoratorText = buildEnumDecorator(typeInfo);
1076
- return fixer.replaceTextRange(
1138
+ const decoratorFix = fixer.replaceTextRange(
1077
1139
  propertyDecorator.range,
1078
1140
  newDecoratorText,
1079
1141
  );
1142
+ const importFix = hasEnumImport()
1143
+ ? null
1144
+ : addEnumImport(fixer);
1145
+
1146
+ return importFix ? [importFix, decoratorFix] : decoratorFix;
1080
1147
  },
1081
1148
  });
1082
1149
  return;
@@ -1088,10 +1155,13 @@ export default createRule<
1088
1155
  messageId: "useEnumDecorator",
1089
1156
  fix: (fixer) => {
1090
1157
  const newDecoratorText = buildEnumDecorator(typeInfo);
1091
- return fixer.insertTextBeforeRange(
1158
+ const decoratorFix = fixer.insertTextBeforeRange(
1092
1159
  member.range,
1093
1160
  newDecoratorText + "\n ",
1094
1161
  );
1162
+ const importFix = hasEnumImport() ? null : addEnumImport(fixer);
1163
+
1164
+ return importFix ? [importFix, decoratorFix] : decoratorFix;
1095
1165
  },
1096
1166
  });
1097
1167
  return;
@@ -1103,8 +1173,15 @@ export default createRule<
1103
1173
  node: member,
1104
1174
  messageId: "alignPropertyDecoratorWithTsType",
1105
1175
  fix: (fixer) => {
1106
- const fixes = addPropertyDecorator(member, typeInfo);
1107
- return applyFixes(fixer, fixes);
1176
+ const decoratorFixes = applyFixes(
1177
+ fixer,
1178
+ addPropertyDecorator(member, typeInfo),
1179
+ );
1180
+ const importFix = addPropertyDecoratorImports(fixer, typeInfo);
1181
+
1182
+ return importFix
1183
+ ? [importFix, ...decoratorFixes]
1184
+ : decoratorFixes;
1108
1185
  },
1109
1186
  });
1110
1187
  return;
@@ -84,6 +84,32 @@ describe("decorators", () => {
84
84
 
85
85
  expect(hasClassDecorator(classDeclaration, "Entity")).toBe(false);
86
86
  });
87
+
88
+ it("should return false for non-call and non-identifier class decorators", () => {
89
+ const classDeclaration = {
90
+ type: AST_NODE_TYPES.ClassDeclaration,
91
+ decorators: [
92
+ {
93
+ type: AST_NODE_TYPES.Decorator,
94
+ expression: {
95
+ type: AST_NODE_TYPES.Identifier,
96
+ name: "Entity",
97
+ },
98
+ },
99
+ {
100
+ type: AST_NODE_TYPES.Decorator,
101
+ expression: {
102
+ type: AST_NODE_TYPES.CallExpression,
103
+ callee: {
104
+ type: AST_NODE_TYPES.MemberExpression,
105
+ },
106
+ },
107
+ },
108
+ ],
109
+ } as unknown as TSESTree.ClassDeclaration;
110
+
111
+ expect(hasClassDecorator(classDeclaration, "Entity")).toBe(false);
112
+ });
87
113
  });
88
114
 
89
115
  describe("hasPropertyDecorator", () => {
@@ -148,6 +174,32 @@ describe("decorators", () => {
148
174
 
149
175
  expect(hasPropertyDecorator(propertyDefinition, "Field")).toBe(false);
150
176
  });
177
+
178
+ it("should return false for non-call and non-identifier property decorators", () => {
179
+ const propertyDefinition = {
180
+ type: AST_NODE_TYPES.PropertyDefinition,
181
+ decorators: [
182
+ {
183
+ type: AST_NODE_TYPES.Decorator,
184
+ expression: {
185
+ type: AST_NODE_TYPES.Identifier,
186
+ name: "Field",
187
+ },
188
+ },
189
+ {
190
+ type: AST_NODE_TYPES.Decorator,
191
+ expression: {
192
+ type: AST_NODE_TYPES.CallExpression,
193
+ callee: {
194
+ type: AST_NODE_TYPES.MemberExpression,
195
+ },
196
+ },
197
+ },
198
+ ],
199
+ } as unknown as TSESTree.PropertyDefinition;
200
+
201
+ expect(hasPropertyDecorator(propertyDefinition, "Field")).toBe(false);
202
+ });
151
203
  });
152
204
 
153
205
  describe("getClassDecorator", () => {