Repository: eslint/eslint (JavaScript)

Issue:
Add sort-imports member sorting into a separate option
**What rule do you want to change?**

`sort-imports`

**Does this change cause the rule to produce more or fewer warnings?**

Fewer

**How will the change be implemented? (New option, new default behavior, etc.)?**

Currently, `sort-imports` does two checks. One is always on, the other is optional. I would like to only use the optional part.

One part is that it checks the order of import statements. This can’t be turned off. I would like to check this using [import/order](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md), which offers more advanced features.

The part of the rule I would like to use, is the sorting of the imported members.

My proposal is to split the distinct `memberSort` option into a separate rule: `sort-import-members`.

**Please provide some example code that this change will affect:**

```js
// Builtins
import { join, resolve } from 'path';

// External
import { map } from 'lodash';
```

**What does the rule currently do for this code?**

`sort-imports` will cause conflicts with `import/order`, but also make sure import members are sorted nicely.

**What will the rule do after it's changed?**

`sort-imports` should be disabled to prevent conflicts with `import/order`.

`sort-import-members` will ensure the imported members are sorted, as `sort-imports` does now.


Discussion:
Given the pluggable nature of ESLint and the size of the plugin community, we can't always ensure that all rules work with plugins. I'm not convinced we need an entirely new rule to solve this problem.

I wonder if a better solve would be to deprecate the [`ignoreMemberSort` option](https://eslint.org/docs/rules/sort-imports#options) in favor of an option that allows the toggling of sorting import declarations and imported members individually. I'm imagining something like:

```json
{
    "sort-imports": ["error", {
        "sortDeclarations": true,
        "sortMembers": true,
    }]
}
```

If we decide to go down this route, we should do this in a backwards compatible way.

That proposal seems just as good to me. :+1: 

For backwards compatibility, I suggest the following signature:

```js
{
  "sort-imports": ["error", {
    ignoreMemberSort: false,  // Already exists
    ignoreDeclarationSort: false,  //  Analogous to `ignoreMemberSort`. The default matches the current behavious.
    ...otherOptions
  }]
}
```
The proposal to add an option to turn off declaration sort seems reasonable to me. :+1:

@remcohaszing are you will to submit a pull request for this proposal?
I'll give it a try somewhere this week.
Can someone look into the related PR ? There hasn't been any action taken in over a month, and I'm also very interested in having a rule to check ordered and sorted imports.
Thank you for your PR @remcohaszing :)
Thanks for the ping, we'll take a look.
**TSC Summary:** This proposal seeks to add another option, `ignoreDeclarationSort` to `sort-imports` to allow turning off a part of the rule that previously was required. The option is backwards-compatible and there is already a pull request (https://github.com/eslint/eslint/pull/11040) for implementation. This issue fell off of the radar and so is missing feedback, but I will champion.

**TSC Questions:**

* Should this proposal be accepted?
* Should https://github.com/eslint/eslint/pull/11040 be merged?
Rule changes do not require TSC consent, unless they are breaking. In that case, it looks like there's a champion and 1 up vote already on the proposal. It's now up to the champion to gather more support from the rest of the team, until this issue can be accepted.
@ilyavolodin oh right, duh! Thanks.
Is any action required from me? I thought not, but it’s taking much longer to get merged than I expected.
@remcohaszing thanks for the ping. Sorry, a lot of team members are taking time off around this time of year, so things can take a bit longer than usual. It looks like enough team members support this change so I'm marking as accepted. The next step is to review the PR.



Patch:
diff --git a/docs/rules/sort-imports.md b/docs/rules/sort-imports.md
index d375279384ea..d885c9c1c4a8 100644
--- a/docs/rules/sort-imports.md
+++ b/docs/rules/sort-imports.md
@@ -34,6 +34,7 @@ The `--fix` option on the command line automatically fixes some problems reporte
 This rule accepts an object with its properties as
 
 * `ignoreCase` (default: `false`)
+* `ignoreDeclarationSort` (default: `false`)
 * `ignoreMemberSort` (default: `false`)
 * `memberSyntaxSortOrder` (default: `["none", "all", "multiple", "single"]`); all 4 items must be present in the array, but you can change the order:
     * `none` = import module without exported bindings.
@@ -47,6 +48,7 @@ Default option settings are:
 {
     "sort-imports": ["error", {
         "ignoreCase": false,
+        "ignoreDeclarationSort": false,
         "ignoreMemberSort": false,
         "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
     }]
@@ -136,6 +138,34 @@ import c from 'baz.js';
 
 Default is `false`.
 
+### `ignoreDeclarationSort`
+
+Ignores the sorting of import declaration statements.
+
+Examples of **incorrect** code for this rule with the default `{ "ignoreDeclarationSort": false }` option:
+
+```js
+/*eslint sort-imports: ["error", { "ignoreDeclarationSort": false }]*/
+import 'foo.js'
+import 'bar.js'
+```
+
+Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option:
+
+```js
+/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/
+import 'foo.js'
+import 'bar.js'
+```
+
+```js
+/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/
+import 'bar.js'
+import 'foo.js'
+```
+
+Default is `false`.
+
 ### `ignoreMemberSort`
 
 Ignores the member sorting within a `multiple` member import declaration.
diff --git a/lib/rules/sort-imports.js b/lib/rules/sort-imports.js
index 8735be5d3071..b47d2a06852d 100644
--- a/lib/rules/sort-imports.js
+++ b/lib/rules/sort-imports.js
@@ -34,6 +34,9 @@ module.exports = {
                         minItems: 4,
                         maxItems: 4
                     },
+                    ignoreDeclarationSort: {
+                        type: "boolean"
+                    },
                     ignoreMemberSort: {
                         type: "boolean"
                     }
@@ -49,6 +52,7 @@ module.exports = {
 
         const configuration = context.options[0] || {},
             ignoreCase = configuration.ignoreCase || false,
+            ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
             ignoreMemberSort = configuration.ignoreMemberSort || false,
             memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
             sourceCode = context.getSourceCode();
@@ -103,44 +107,48 @@ module.exports = {
 
         return {
             ImportDeclaration(node) {
-                if (previousDeclaration) {
-                    const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
-                        previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
-                    let currentLocalMemberName = getFirstLocalMemberName(node),
-                        previousLocalMemberName = getFirstLocalMemberName(previousDeclaration);
-
-                    if (ignoreCase) {
-                        previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase();
-                        currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase();
-                    }
-
-                    /*
-                     * When the current declaration uses a different member syntax,
-                     * then check if the ordering is correct.
-                     * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name.
-                     */
-                    if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) {
-                        if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) {
-                            context.report({
-                                node,
-                                message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.",
-                                data: {
-                                    syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
-                                    syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
-                                }
-                            });
+                if (!ignoreDeclarationSort) {
+                    if (previousDeclaration) {
+                        const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
+                            previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
+                        let currentLocalMemberName = getFirstLocalMemberName(node),
+                            previousLocalMemberName = getFirstLocalMemberName(previousDeclaration);
+
+                        if (ignoreCase) {
+                            previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase();
+                            currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase();
                         }
-                    } else {
-                        if (previousLocalMemberName &&
-                            currentLocalMemberName &&
-                            currentLocalMemberName < previousLocalMemberName
-                        ) {
-                            context.report({
-                                node,
-                                message: "Imports should be sorted alphabetically."
-                            });
+
+                        /*
+                         * When the current declaration uses a different member syntax,
+                         * then check if the ordering is correct.
+                         * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name.
+                         */
+                        if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) {
+                            if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) {
+                                context.report({
+                                    node,
+                                    message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.",
+                                    data: {
+                                        syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
+                                        syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
+                                    }
+                                });
+                            }
+                        } else {
+                            if (previousLocalMemberName &&
+                                currentLocalMemberName &&
+                                currentLocalMemberName < previousLocalMemberName
+                            ) {
+                                context.report({
+                                    node,
+                                    message: "Imports should be sorted alphabetically."
+                                });
+                            }
                         }
                     }
+
+                    previousDeclaration = node;
                 }
 
                 if (!ignoreMemberSort) {
@@ -189,8 +197,6 @@ module.exports = {
                         });
                     }
                 }
-
-                previousDeclaration = node;
             }
         };
     }


Test patch:
diff --git a/tests/lib/rules/sort-imports.js b/tests/lib/rules/sort-imports.js
index 4cee0a2c5bba..290505650ea6 100644
--- a/tests/lib/rules/sort-imports.js
+++ b/tests/lib/rules/sort-imports.js
@@ -61,6 +61,14 @@ ruleTester.run("sort-imports", rule, {
             options: ignoreCaseArgs
         },
         "import {a, b, c, d} from 'foo.js';",
+        {
+            code:
+                "import a from 'foo.js';\n" +
+                "import B from 'bar.js';",
+            options: [{
+                ignoreDeclarationSort: true
+            }]
+        },
         {
             code: "import {b, A, C, d} from 'foo.js';",
             options: [{
@@ -175,6 +183,21 @@ ruleTester.run("sort-imports", rule, {
                 type: "ImportSpecifier"
             }]
         },
+        {
+            code:
+                "import {b, a, d, c} from 'foo.js';\n" +
+                "import {e, f, g, h} from 'bar.js';",
+            output:
+                "import {a, b, c, d} from 'foo.js';\n" +
+                "import {e, f, g, h} from 'bar.js';",
+            options: [{
+                ignoreDeclarationSort: true
+            }],
+            errors: [{
+                message: "Member 'a' of the import declaration should be sorted alphabetically.",
+                type: "ImportSpecifier"
+            }]
+        },
         {
             code: "import {a, B, c, D} from 'foo.js';",
             output: "import {B, D, a, c} from 'foo.js';",