Skip to content
This repository has been archived by the owner on Sep 30, 2020. It is now read-only.

Commit

Permalink
feat!: add sort-import-destructures rule
Browse files Browse the repository at this point in the history
Closes: #124
  • Loading branch information
wincent committed Feb 10, 2020
1 parent 264f107 commit e6a2adf
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ If we were to provide configuration by default, then if `bottom-level/.eslintrc.
| [liferay/no-require-and-call](./plugins/eslint-plugin-liferay/docs/rules/no-require-and-call.md) | liferay | [\#94](https://github.com/liferay/eslint-config-liferay/issues/94) |
| [liferay/padded-test-blocks](./plugins/eslint-plugin-liferay/docs/rules/padded-test-blocks.md) | liferay | [\#75](https://github.com/liferay/eslint-config-liferay/pull/75) |
| [liferay/sort-imports](./plugins/eslint-plugin-liferay/docs/rules/sort-imports.md) | liferay | [\#60](https://github.com/liferay/liferay-frontend-guidelines/issues/60) |
| [liferay/sort-import-destructures](./plugins/eslint-plugin-liferay/docs/rules/sort-import-destructures.md) | liferay | [\#124](https://github.com/liferay/eslint-config-liferay/issues/124) |
| [liferay/sort-class-names](./plugins/eslint-plugin-liferay/docs/rules/sort-class-names.md) | liferay | [\#108](https://github.com/liferay/eslint-config-liferay/issues/108) |
| [liferay/trim-class-names](./plugins/eslint-plugin-liferay/docs/rules/trim-class-names.md) | liferay | [\#108](https://github.com/liferay/eslint-config-liferay/issues/108) |
| [no-console](https://eslint.org/docs/rules/no-console) | liferay | [\#79](https://github.com/liferay/eslint-config-liferay/pull/79) |
Expand Down Expand Up @@ -175,6 +176,7 @@ The bundled `eslint-plugin-liferay` plugin includes the following [rules](./plug
- [liferay/padded-test-blocks](./plugins/eslint-plugin-liferay/docs/rules/padded-test-blocks.md): Enforces blank lines between test blocks (`it()` etc).
- [liferay/sort-class-names](./plugins/eslint-plugin-liferay/docs/rules/sort-class-names.md): Enforces (and autofixes) ordering of class names inside JSX `className` attributes.
- [liferay/sort-imports](./plugins/eslint-plugin-liferay/docs/rules/sort-imports.md): Enforces (and autofixes) `import` and `require` ordering.
- [liferay/sort-import-destructures](./plugins/eslint-plugin-liferay/docs/rules/sort-import-destructures.md): Enforces (and autofixes) ordering of destructured names in `import` statements.
- [liferay/trim-class-names](./plugins/eslint-plugin-liferay/docs/rules/trim-class-names.md): Enforces (and autofixes) that class names inside JSX `className` attributes do not have leading or trailing whitespace.

#### `eslint-plugin-liferay-portal`
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const config = {
'liferay/no-it-should': 'error',
'liferay/no-require-and-call': 'error',
'liferay/padded-test-blocks': 'error',
'liferay/sort-import-destructures': 'error',
'liferay/sort-imports': 'error',
'no-console': ['error', {allow: ['warn', 'error']}],
'no-constant-condition': ['error', {checkLoops: false}],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Sort destructured names in `import` statements (sort-import-destructures)

This rule enforces (and autofixes) that destructured names in `import` statements are sorted.

## Rule Details

Examples of **incorrect** code for this rule:

```js
import {xyz, abc} from 'other';

import main, {second as alias, first as nickname} from 'something';
```

Examples of **correct** code for this rule:

```js
import {abc, xyz} from 'other';

import main, {first as nickname, second as alias} from 'something';
```

Note how the sorting is based on the name of the imported export (eg. `first`, `second`) and not the local name (eg. `nickname`, `alias`).

## Further Reading

- https://github.com/liferay/eslint-config-liferay/issues/124
1 change: 1 addition & 0 deletions plugins/eslint-plugin-liferay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
'no-require-and-call': require('./lib/rules/no-require-and-call'),
'padded-test-blocks': require('./lib/rules/padded-test-blocks'),
'sort-class-names': require('./lib/rules/sort-class-names'),
'sort-import-destructures': require('./lib/rules/sort-import-destructures'),
'sort-imports': require('./lib/rules/sort-imports'),
'trim-class-names': require('./lib/rules/trim-class-names'),
},
Expand Down
101 changes: 101 additions & 0 deletions plugins/eslint-plugin-liferay/lib/rules/sort-import-destructures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* © 2017 Liferay, Inc. <https://liferay.com>
*
* SPDX-License-Identifier: MIT
*/

const DESCRIPTION = 'destructured names in imports must be sorted';

module.exports = {
create(context) {
return {
ImportDeclaration(node) {
const specifiers = node.specifiers.filter(specifier => {
// Just `ImportSpecifier` (ignore `ImportDefaultSpecifier`).
return specifier.type === 'ImportSpecifier';
});

if (specifiers.length > 1) {
const source = context.getSourceCode();

if (
source.commentsExistBetween(
source.getTokenBefore(specifiers[0]),
node.source
)
) {
// Don't touch if any of the specifiers have
// comments.
return;
}

let fix;

// Given:
//
// import {a as b, c} from 'd';
//
// We'll have two specifiers:
//
// - `imported.name === 'a'`, `local.name === 'b').
// - `imported.name === 'c'`.
//
// We sort by `imported` always, ignoring `local`.
const sorted = specifiers.slice().sort((a, b) => {
const order =
a.imported.name > b.imported.name ? 1 : -1;

if (order === 1) {
fix = true;
}

return order;
});

if (fix) {
const text =
' '.repeat(node.start) + source.getText(node);

const start = specifiers[0].start;
const end = specifiers[specifiers.length - 1].end;

let fixed = '';

for (let i = 0; i < specifiers.length; i++) {
fixed += source.getText(sorted[i]);

if (i < specifiers.length - 1) {
// Grab all text between specifier and next.
const between = text.slice(
specifiers[i].end,
specifiers[i + 1].start
);

fixed += between;
}
}

context.report({
fix: fixer =>
fixer.replaceTextRange([start, end], fixed),
message: DESCRIPTION,
node,
});
}
}
},
};
},

meta: {
docs: {
category: 'Best Practices',
description: DESCRIPTION,
recommended: false,
url: 'https://github.com/liferay/eslint-config-liferay/issues/124',
},
fixable: 'code',
schema: [],
type: 'problem',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* © 2017 Liferay, Inc. <https://liferay.com>
*
* SPDX-License-Identifier: MIT
*/

const {RuleTester} = require('eslint');

const rule = require('../../../lib/rules/sort-import-destructures');

const parserOptions = {
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
},
};

const ruleTester = new RuleTester(parserOptions);

ruleTester.run('sort-import-destructures', rule, {
invalid: [
{
code: `import {z, g} from 'a';`,
errors: [
{
message: 'destructured names in imports must be sorted',
},
],
output: `import {g, z} from 'a';`,
},
{
code: `import {b as bar, a as foo} from 'b';`,
errors: [
{
message: 'destructured names in imports must be sorted',
},
],
output: `import {a as foo, b as bar} from 'b';`,
},
{
code: `import thing, {k, h, j} from 'c';`,
errors: [
{
message: 'destructured names in imports must be sorted',
},
],
output: `import thing, {h, j, k} from 'c';`,
},
{
// Same as previous, but with line breaks.
code: `
import thing, {
k,
h,
j
} from 'c';
`,
errors: [
{
message: 'destructured names in imports must be sorted',
},
],
output: `
import thing, {
h,
j,
k
} from 'c';
`,
},
{
// Note that trailing commas are preserved.
code: `
import {
z,
y,
x,
} from 'file';
`,
errors: [
{
message: 'destructured names in imports must be sorted',
},
],
output: `
import {
x,
y,
z,
} from 'file';
`,
},
],

valid: [
{
code: `import thing from 'thing';`,
},
{
code: `import {gizmo} from 'gizmo';`,
},
{
code: `import {g, z} from 'a';`,
},
{
code: `import {a as foo, b as bar} from 'b';`,
},
{
code: `import thing, {h, j, k} from 'c';`,
},
{
// We don't touch the sort order if there are comments anywhere.
code: `
import {
// Comment.
zoo,
school
} from 'places';
import {
zzz, // Comment.
xxx
} from 'letters';
import {
three,
two,
one // Comment.
} from 'numbers';
`,
},
],
});
5 changes: 0 additions & 5 deletions plugins/eslint-plugin-liferay/tests/lib/rules/sort-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ const parserOptions = {

const ruleTester = new RuleTester(parserOptions);

// TODO: make sure destructuring is ordered too
// eg. https://github.com/mthadley/eslint-plugin-sort-destructure-keys
// eslint's sort-imports plug-in sorts destructuring patterns too
// https://github.com/eslint/eslint/blob/master/lib/rules/sort-imports.js
// i think these need to be separate rules
ruleTester.run('sort-imports', rule, {
invalid: [
{
Expand Down

0 comments on commit e6a2adf

Please sign in to comment.