Repository: un-ts/eslint-plugin-import-x
Pull request #206 (2024-12-18 10:34:54)

 fix(no-duplicates): type prefix without new line
When merging imports, the `type` prefix would get inserted on a different line.

This changes the auto-fix so it inserts new type imports after the last token inside the import bracket, as opposed to before the closing bracket token.

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: d4a6846e901ff01132e9d81e50250a276a34f918

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/privatenumber/eslint-plugin-import-x/new/type-prefix?filename=.changeset/tricky-baboons-reflect.md&value=---%0A%22eslint-plugin-import-x%22%3A%20patch%0A---%0A%0A%20fix(no-duplicates)%3A%20type%20prefix%20without%20new%20line%0A)


codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/un-ts/eslint-plugin-import-x/pr/206/builds/571853) or the icon next to each commit SHA.

Patch:
diff --git a/.changeset/tame-melons-notice.md b/.changeset/tame-melons-notice.md
new file mode 100644
index 00000000..b13398c7
--- /dev/null
+++ b/.changeset/tame-melons-notice.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-import-x": patch
+---
+
+insert type prefix without new line
diff --git a/src/rules/no-duplicates.ts b/src/rules/no-duplicates.ts
index b4732f75..2a5d5893 100644
--- a/src/rules/no-duplicates.ts
+++ b/src/rules/no-duplicates.ts
@@ -172,13 +172,15 @@ function getFix(
           specifier.identifiers.reduce(
             ([text, set], cur) => {
               const trimmed = cur.trim() // Trim whitespace before/after to compare to our set of existing identifiers
-              const curWithType =
-                trimmed.length > 0 && preferInline && isTypeSpecifier
-                  ? `type ${cur}`
-                  : cur
-              if (existingIdentifiers.has(trimmed)) {
+              if (trimmed.length === 0 || existingIdentifiers.has(trimmed)) {
                 return [text, set]
               }
+
+              const curWithType =
+                preferInline && isTypeSpecifier
+                  ? cur.replace(/^(\s*)/, '$1type ')
+                  : cur
+
               return [
                 text.length > 0 ? `${text},${curWithType}` : curWithType,
                 set.add(trimmed),
@@ -267,8 +269,9 @@ function getFix(
         )
       }
     } else if (openBrace != null && closeBrace != null && !shouldAddDefault()) {
+      const tokenBefore = sourceCode.getTokenBefore(closeBrace)!
       // `import {...} './foo'` → `import {..., ...} from './foo'`
-      fixes.push(fixer.insertTextBefore(closeBrace, specifiersText))
+      fixes.push(fixer.insertTextAfter(tokenBefore, specifiersText))
     }
 
     // Remove imports whose specifiers have been moved into the first import.


Test patch:
diff --git a/test/rules/no-duplicates.spec.ts b/test/rules/no-duplicates.spec.ts
index f71a0d54..e9636fdc 100644
--- a/test/rules/no-duplicates.spec.ts
+++ b/test/rules/no-duplicates.spec.ts
@@ -70,7 +70,7 @@ ruleTester.run('no-duplicates', rule, {
   invalid: [
     tInvalid({
       code: "import { x } from './foo'; import { y } from './foo'",
-      output: "import { x , y } from './foo'; ",
+      output: "import { x, y  } from './foo'; ",
       errors: [createDuplicatedError('./foo'), createDuplicatedError('./foo')],
     }),
 
@@ -87,7 +87,7 @@ ruleTester.run('no-duplicates', rule, {
     // ensure resolved path results in warnings
     tInvalid({
       code: "import { x } from './bar'; import { y } from 'bar';",
-      output: "import { x , y } from './bar'; ",
+      output: "import { x, y  } from './bar'; ",
       settings: {
         'import-x/resolve': {
           paths: [path.resolve('test/fixtures')],
@@ -133,7 +133,7 @@ ruleTester.run('no-duplicates', rule, {
 
     tInvalid({
       code: "import type { x } from './foo'; import type { y } from './foo'",
-      output: "import type { x , y } from './foo'; ",
+      output: "import type { x, y  } from './foo'; ",
       languageOptions: { parser: require(parsers.BABEL) },
       errors: [createDuplicatedError('./foo'), createDuplicatedError('./foo')],
     }),
@@ -146,7 +146,7 @@ ruleTester.run('no-duplicates', rule, {
 
     tInvalid({
       code: "import { x, /* x */ } from './foo'; import {//y\ny//y2\n} from './foo'",
-      output: "import { x, /* x */ //y\ny//y2\n} from './foo'; ",
+      output: "import { x,//y\ny//y2\n /* x */ } from './foo'; ",
       languageOptions: { parser: require(parsers.ESPREE) },
       errors: [createDuplicatedError('./foo'), createDuplicatedError('./foo')],
     }),
@@ -195,7 +195,7 @@ ruleTester.run('no-duplicates', rule, {
 
     tInvalid({
       code: "import { } from './foo'; import {x} from './foo'",
-      output: "import { x} from './foo'; ",
+      output: "import {x } from './foo'; ",
       errors: [createDuplicatedError('./foo'), createDuplicatedError('./foo')],
     }),
 
@@ -419,7 +419,7 @@ import {x,y} from './foo'
     // #2027 long import list generate empty lines
     tInvalid({
       code: "import { Foo } from './foo';\nimport { Bar } from './foo';\nexport const value = {}",
-      output: "import { Foo , Bar } from './foo';\nexport const value = {}",
+      output: "import { Foo, Bar  } from './foo';\nexport const value = {}",
       errors: [createDuplicatedError('./foo'), createDuplicatedError('./foo')],
     }),
 
@@ -452,8 +452,8 @@ export default TestComponent;
 import {
   DEFAULT_FILTER_KEYS,
   BULK_DISABLED,
-
   BULK_ACTIONS_ENABLED
+
 } from '../constants';
 import React from 'react';
 
@@ -493,9 +493,9 @@ export default TestComponent;
                 ${''}
         import {
           A2,
-        ${''}
           B2,
-          C2} from 'bar';
+          C2
+        } from 'bar';
                 ${''}
       `,
       errors: [
@@ -822,7 +822,7 @@ describe('TypeScript', () => {
             code: "import type { AType as BType } from './foo'; import { CValue } from './foo'",
             ...parserConfig,
             options: [{ 'prefer-inline': true }],
-            output: `import { type AType as BType , CValue } from './foo'; `,
+            output: `import { type AType as BType, CValue  } from './foo'; `,
             errors: [
               {
                 ...createDuplicatedError('./foo'),
@@ -836,6 +836,37 @@ describe('TypeScript', () => {
               },
             ],
           }),
+          tInvalid({
+            code: `
+            import {
+              a
+            } from './foo';
+            import type {
+              b,
+              c,
+            } from './foo';`,
+            ...parserConfig,
+            options: [{ 'prefer-inline': true }],
+            output: `
+            import {
+              a,
+              type b,
+              type c
+            } from './foo';
+            `,
+            errors: [
+              {
+                ...createDuplicatedError('./foo'),
+                line: 4,
+                column: 20,
+              },
+              {
+                ...createDuplicatedError('./foo'),
+                line: 8,
+                column: 20,
+              },
+            ],
+          }),
         ]),
   ]