Skip to content

[New] order: add caseInsensitive: 'invert' option #1740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/rules/order.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,12 @@ import index from './';
import sibling from './foo';
```

### `alphabetize: {order: asc|desc|ignore, caseInsensitive: true|false}`:
### `alphabetize: {order: asc|desc|ignore, caseInsensitive: true|false|'invert'}`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshed: maybe in addition to true/false, we could support (and recommend) a three-option string enum? iow, this can be 'ignore' | 'upperFirst' | 'lowerFirst', with false being an alias for ignore and true being an alias for upperFirst?

Copy link
Contributor Author

@forivall forivall Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, perhaps the "caseInsensitive" should be deprecated and make the 3 option string enum under an option called "caseSensitive" or "caseSensitivity"?

i wonder what other things call their options?

With that all said, i'd like to suggest keeping caseInsensitive as a boolean, and for this option, introduce caseFirst: 'upper' | 'lower', based on the Intl.Collator options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably also be a good idea to just use Intl.Collator like what typescript-eslint does instead of doing the case-munging manually

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternative diff, using caseFirst is here: master...forivall:feat/case-first


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`).
- `caseInsensitive`: use `true` to ignore case, `false` to consider case, and `'invert'` to sort lowercase before uppercase (default: `false`).

Example setting:
```js
Expand Down
38 changes: 29 additions & 9 deletions src/rules/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,18 @@ function makeOutOfOrderReport(context, imported) {
reportOutOfOrder(context, imported, outOfOrder, 'before');
}

function getSorter(ascending) {
const multiplier = ascending ? 1 : -1;
function getSorter(alphabetizeOptions) {
const multiplier = alphabetizeOptions.order === 'asc' ? 1 : -1;
let collate;
if (alphabetizeOptions.caseInsensitive) {
if (alphabetizeOptions.caseFirst === 'lower') {
collate = swapCase;
} else {
collate = (s) => String(s).toLowerCase();
}
}

return function importsSorter(importA, importB) {
function importsSorter(importA, importB) {
let result;

if (importA < importB) {
Expand All @@ -257,7 +265,18 @@ function getSorter(ascending) {
}

return result * multiplier;
};
}
return collate ? (a, b) => importsSorter(collate(a), collate(b)) : importsSorter;
}

function swapCase(input) {
input = String(input);
let result = '';
for (let i = 0; i < input.length; i++) {
const lower = input[i].toLowerCase();
result += input[i] === lower ? input[i].toUpperCase() : lower;
}
return result;
}

function mutateRanksToAlphabetize(imported, alphabetizeOptions) {
Expand All @@ -271,11 +290,10 @@ function mutateRanksToAlphabetize(imported, alphabetizeOptions) {

const groupRanks = Object.keys(groupedByRanks);

const sorterFn = getSorter(alphabetizeOptions.order === 'asc');
const comparator = alphabetizeOptions.caseInsensitive ? (a, b) => sorterFn(String(a).toLowerCase(), String(b).toLowerCase()) : (a, b) => sorterFn(a, b);
const sorterFn = getSorter(alphabetizeOptions);
// sort imports locally within their group
groupRanks.forEach(function(groupRank) {
groupedByRanks[groupRank].sort(comparator);
groupedByRanks[groupRank].sort(sorterFn);
});

// assign globally unique rank to each import
Expand Down Expand Up @@ -556,8 +574,10 @@ module.exports = {
type: 'object',
properties: {
caseInsensitive: {
type: 'boolean',
default: false,
anyOf: [
{ type: 'boolean', default: false },
{ type: 'string', enum: ['invert'] },
],
},
order: {
enum: ['ignore', 'asc', 'desc'],
Expand Down
48 changes: 48 additions & 0 deletions tests/src/rules/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -2121,6 +2121,54 @@ ruleTester.run('order', rule, {
message: '`foo` import should occur before import of `Bar`',
}],
}),
// Option alphabetize {order: 'asc': caseInsensitive: 'invert'}
test({
code: `
import a from 'bar';
import b from 'Foo';
import c from 'foo';

import index from './';
`,
output: `
import a from 'bar';
import c from 'foo';
import b from 'Foo';

import index from './';
`,
options: [{
groups: ['external', 'index'],
alphabetize: { order: 'asc', caseInsensitive: 'invert' },
}],
errors: [{
message: '`foo` import should occur before import of `Foo`',
}],
}),
// Option alphabetize {order: 'desc': caseInsensitive: 'invert'}
test({
code: `
import a from 'foo';
import b from 'Foo';
import c from 'bar';

import index from './';
`,
output: `
import b from 'Foo';
import a from 'foo';
import c from 'bar';

import index from './';
`,
options: [{
groups: ['external', 'index'],
alphabetize: { order: 'desc', caseInsensitive: 'invert' },
}],
errors: [{
message: '`Foo` import should occur before import of `foo`',
}],
}),
// Alphabetize with parent paths
test({
code: `
Expand Down