Repository: import-js/eslint-plugin-import (JavaScript)

Issue:
Option to make "alphabetize" sorting case insensitive
The new "alphabetize" option is great! For those of us migrating from tslint to eslint, it would be nice if we could have an extra option to make this alphabetical sorting case insensitive.


Discussion:
In general, I don't think "tslint has it" is sufficient justification to add anything automatically. However, in this case, that seems like a reasonable option.
@ljharb when do you think the next release will be that will have this change?
When i have time to make it.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b64f1d8240..cbd12b251a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
 
 ## [Unreleased]
+### Added
+- [`order`]: added `caseInsensitive` as an additional option to `alphabetize` ([#1586], thanks [@dbrewer5])
+
 ### Fixed
 - [`no-unused-modules`]: fix usage of `import/extensions` settings ([#1560], thanks [@stekycz])
 
@@ -625,6 +628,7 @@ for info on changes for earlier releases.
 
 [`memo-parser`]: ./memo-parser/README.md
 
+[#1586]: https://github.com/benmosher/eslint-plugin-import/pull/1586
 [#1563]: https://github.com/benmosher/eslint-plugin-import/pull/1563
 [#1560]: https://github.com/benmosher/eslint-plugin-import/pull/1560
 [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551
@@ -1046,3 +1050,4 @@ for info on changes for earlier releases.
 [@randallreedjr]: https://github.com/randallreedjr
 [@Pessimistress]: https://github.com/Pessimistress
 [@stekycz]: https://github.com/stekycz
+[@dbrewer5]: https://github.com/dbrewer5
diff --git a/docs/rules/order.md b/docs/rules/order.md
index b5c4902ac8..8012e637f7 100644
--- a/docs/rules/order.md
+++ b/docs/rules/order.md
@@ -189,16 +189,18 @@ import index from './';
 import sibling from './foo';
 ```
 
-### `alphabetize: {order: asc|desc|ignore}`:
+### `alphabetize: {order: asc|desc|ignore, caseInsensitive: true|false}`:
 
 Sort the order within each group in alphabetical manner based on **import path**:
 
 - `order`: use `asc` to sort in ascending order, and `desc` to sort in descending order (default: `ignore`).
+- `caseInsensitive`: use `true` to ignore case, and `false` to consider case (default: `false`).
 
 Example setting:
 ```js
 alphabetize: {
   order: 'asc', /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */
+  caseInsensitive: true /* ignore case. Options: [true, false] */
 }
 ```
 
@@ -210,12 +212,14 @@ import React, { PureComponent } from 'react';
 import aTypes from 'prop-types';
 import { compose, apply } from 'xcompose';
 import * as classnames from 'classnames';
+import blist from 'BList';
 ```
 
 While this will pass:
 
 ```js
 /* eslint import/order: ["error", {"alphabetize": true}] */
+import blist from 'BList';
 import * as classnames from 'classnames';
 import aTypes from 'prop-types';
 import React, { PureComponent } from 'react';
diff --git a/src/rules/order.js b/src/rules/order.js
index 7a7629bf7f..a842f60afb 100644
--- a/src/rules/order.js
+++ b/src/rules/order.js
@@ -267,7 +267,7 @@ function importsSorterDesc(importA, importB) {
   return 0
 }
 
-function mutateRanksToAlphabetize(imported, order) {
+function mutateRanksToAlphabetize(imported, alphabetizeOptions) {
   const groupedByRanks = imported.reduce(function(acc, importedItem) {
     if (!Array.isArray(acc[importedItem.rank])) { 
       acc[importedItem.rank] = []
@@ -278,10 +278,11 @@ function mutateRanksToAlphabetize(imported, order) {
 
   const groupRanks = Object.keys(groupedByRanks)
 
-  const sorterFn = order === 'asc' ? importsSorterAsc : importsSorterDesc
+  const sorterFn = alphabetizeOptions.order === 'asc' ? importsSorterAsc : importsSorterDesc
+  const comparator = alphabetizeOptions.caseInsensitive ? (a, b) => sorterFn(String(a).toLowerCase(), String(b).toLowerCase()) : (a, b) => sorterFn(a, b)
   // sort imports locally within their group
   groupRanks.forEach(function(groupRank) {
-    groupedByRanks[groupRank].sort(sorterFn)
+    groupedByRanks[groupRank].sort(comparator)
   })
 
   // assign globally unique rank to each import
@@ -487,8 +488,9 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) {
 function getAlphabetizeConfig(options) {
   const alphabetize = options.alphabetize || {}
   const order = alphabetize.order || 'ignore'
+  const caseInsensitive = alphabetize.caseInsensitive || false
 
-  return {order}
+  return {order, caseInsensitive}
 }
 
 module.exports = {
@@ -540,6 +542,10 @@ module.exports = {
           alphabetize: {
             type: 'object',
             properties: {
+              caseInsensitive: {
+                type: 'boolean',
+                default: false,
+              },
               order: {
                 enum: ['ignore', 'asc', 'desc'],
                 default: 'ignore',
@@ -604,7 +610,7 @@ module.exports = {
         }
 
         if (alphabetize.order !== 'ignore') {
-          mutateRanksToAlphabetize(imported, alphabetize.order)
+          mutateRanksToAlphabetize(imported, alphabetize)
         }
 
         makeOutOfOrderReport(context, imported)


Test patch:
diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js
index 153a923380..11309f933b 100644
--- a/tests/src/rules/order.js
+++ b/tests/src/rules/order.js
@@ -1870,5 +1870,51 @@ ruleTester.run('order', rule, {
         message: '`bar` import should occur before import of `Bar`',
       }],
     }),
+    // Option alphabetize {order: 'asc': caseInsensitive: true}
+    test({
+      code: `
+        import b from 'foo';
+        import a from 'Bar';
+
+        import index from './';
+      `,
+      output: `
+        import a from 'Bar';
+        import b from 'foo';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: {order: 'asc', caseInsensitive: true},
+      }],
+      errors: [{
+        ruleID: 'order',
+        message: '`Bar` import should occur before import of `foo`',
+      }],
+    }),
+    // Option alphabetize {order: 'desc': caseInsensitive: true}
+    test({
+      code: `
+        import a from 'Bar';
+        import b from 'foo';
+
+        import index from './';
+      `,
+      output: `
+        import b from 'foo';
+        import a from 'Bar';
+
+        import index from './';
+      `,
+      options: [{
+        groups: ['external', 'index'],
+        alphabetize: {order: 'desc', caseInsensitive: true},
+      }],
+      errors: [{
+        ruleID: 'order',
+        message: '`foo` import should occur before import of `Bar`',
+      }],
+    }),
   ].filter((t) => !!t),
 })