Skip to content
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

Allow unassigned imports #37

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
56 changes: 55 additions & 1 deletion docs/rules/order-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import main from './';

Notes:

- Unassigned imports are ignored (ex: `import 'polyfill'`), as the order they are imported in may be important.
- Unassigned imports are ignored (ex: `import 'polyfill'`), as the order they are imported in may be important. Use 'unassignedImports' option if you'd like to allow them.
- Statements using the ES6 `import` syntax must appear before any `require()` statements.

## Usage
Expand Down Expand Up @@ -149,6 +149,60 @@ import bar from 'bar';
import Baz from 'Baz';
```

### `unassignedImports: [ignore|allow] (default: ignore)`

Unassigned imports refers to imports which are not assigned to any variable but are imported globally.

Example:
```js
import 'polyfill'
import 'styles.scss'
```

By default unassigned imports are ignored, as the order they are imported in may be important.

- If set to `allow`, considers unassigned imports like any other imports when ordering.
- If set to `ignore`, does not consider the ordering for this import.

Examples:

#### ignore
```js
/* eslint import-helpers/order-imports: [
"error",
{
unassignedImports: 'ignore',
groups: [['module'], '/\.scss$/']
},
] */

/* Any placement of 'styles.scss' is VALID */
import 'styles.scss';
import fs from 'fs';
import path from 'path';
```

#### allow
```js
/* eslint import-helpers/order-imports: [
"error",
{
unassignedImports: 'allow',
groups: [['module'], '/\.scss$/']
},
] */

/* INVALID */
import 'styles.scss'
import fs from 'fs';
import path from 'path';

/* VALID */
import fs from 'fs';
import path from 'path';
import 'styles.scss'
```

## Upgrading from v0.14 to v1

### `builtin` | `external` | `internal` → `module`
Expand Down
38 changes: 28 additions & 10 deletions src/rules/order-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ const alphabetizeOptions: AlphabetizeOption[] = ['ignore', 'asc', 'desc'];
type Groups = (ValidImportType | ValidImportType[])[];
const defaultGroups: Groups = ['absolute', 'module', 'parent', 'sibling', 'index'];

type UnassignedImportsOption = 'allow' | 'ignore';
const unassignedImportsOption: UnassignedImportsOption[] = ['allow', 'ignore'];

type RuleOptions = {
groups?: Groups;
newlinesBetween?: NewLinesBetweenOption;
alphabetize?: Partial<AlphabetizeConfig>;
unassignedImports?: UnassignedImportsOption;
};

type ImportType = 'require' | 'import';
Expand Down Expand Up @@ -178,21 +182,25 @@ function isPlainRequireModule(node): boolean {
);
}

function isPlainImportModule(node: NodeOrToken): boolean {
return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0;
const isUnassignedImportsAllowed = (context) => getOptions(context).unassignedImports === 'allow';

function isAllowedImportModule(node: NodeOrToken, context): boolean {
const hasNodeSpecifier = node.specifiers != null && node.specifiers.length > 0;

return node.type === 'ImportDeclaration' && (hasNodeSpecifier || isUnassignedImportsAllowed(context));
}

function canCrossNodeWhileReorder(node: NodeOrToken): boolean {
return isPlainRequireModule(node) || isPlainImportModule(node);
function canCrossNodeWhileReorder(node: NodeOrToken, context): boolean {
return isPlainRequireModule(node) || isAllowedImportModule(node, context);
Copy link
Owner

Choose a reason for hiding this comment

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

I suggest leaving the isPlainImportModule function alone and instead, on this line, add in a check for

I think it would be:

return getOptions(context).unassignedImports === 'allow'` || isPlainRequireModule(node) || isAllowedImportModule(node)

Copy link
Owner

Choose a reason for hiding this comment

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

not 100% sure on this, I think this would also make it sort on variables or other types of nodes

Copy link
Author

Choose a reason for hiding this comment

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

@Tibfib Yeah, I was thinking that too but wanted to make sure I don't let any other node which may not be an import pass through.

}

function canReorderItems(firstNode: NodeOrToken, secondNode: NodeOrToken): boolean {
function canReorderItems(firstNode: NodeOrToken, secondNode: NodeOrToken, context): boolean {
const parent = firstNode.parent;
const firstIndex = parent.body.indexOf(firstNode);
const secondIndex = parent.body.indexOf(secondNode);
const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1);
for (var nodeBetween of nodesBetween) {
if (!canCrossNodeWhileReorder(nodeBetween)) {
if (!canCrossNodeWhileReorder(nodeBetween, context)) {
return false;
}
}
Expand All @@ -209,7 +217,7 @@ function fixOutOfOrder(context, firstNode: NodeOrToken, secondNode: NodeOrToken,
const secondRoot = findRootNode(secondNode.node);
const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot);
const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot);
const canFix = canReorderItems(firstRoot, secondRoot);
const canFix = canReorderItems(firstRoot, secondRoot, context);

let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd);
if (newCode[newCode.length - 1] !== '\n') {
Expand Down Expand Up @@ -478,6 +486,12 @@ function getAlphabetizeConfig(options: RuleOptions): AlphabetizeConfig {
return { order, ignoreCase };
}

function getOptions(context) {
const options: RuleOptions = context.options[0] || {};

return options;
}

module.exports = {
meta: {
type: 'suggestion',
Expand All @@ -496,6 +510,9 @@ module.exports = {
newlinesBetween: {
enum: newLinesBetweenOptions,
},
unassignedImports: {
enum: unassignedImportsOption,
},
alphabetize: {
type: 'object',
properties: {
Expand All @@ -516,7 +533,7 @@ module.exports = {
},

create: function importOrderRule(context) {
const options: RuleOptions = context.options[0] || {};
const options = getOptions(context);
const newlinesBetweenImports: NewLinesBetweenOption = options.newlinesBetween || 'ignore';

let alphabetize: AlphabetizeConfig;
Expand All @@ -543,14 +560,15 @@ module.exports = {

return {
ImportDeclaration: function handleImports(node) {
if (node.specifiers.length) {
if (isAllowedImportModule(node, context)) {
// Ignoring unassigned imports
const name: string = node.source.value;
registerNode(node, name, 'import', ranks, regExpGroups, imported);
}
},
CallExpression: function handleRequires(node) {
if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) {
const isUnassignedRequire = !isInVariableDeclarator(node.parent);
if (level !== 0 || !isStaticRequire(node) || (!isUnassignedImportsAllowed(context) && isUnassignedRequire)) {
return;
}
const name: string = node.arguments[0].value;
Expand Down
29 changes: 29 additions & 0 deletions test/rules/order-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ ruleTester.run('order', rule, {
import path from 'path';
`,
}),
// Consider unassigned values when option is provided (import)
test({
code: `
import 'fs';
import path from 'path';
import './foo';
`,
options: [{ unassignedImports: 'allow' }],
},),
// No imports
test({
code: `
Expand Down Expand Up @@ -1471,5 +1480,25 @@ comment3 */", // the spacing here is really sensitive
},
],
}),
// Option unassignedImports: 'allow' should consider unassigned module imports
test({
code: `
import './foo';
import 'fs';
import path from 'path';
`,
output: `
import 'fs';
import path from 'path';
import './foo';
`,
options: [{ unassignedImports: 'allow' }],
errors: [
{
line: 2,
message: '`./foo` import should occur after import of `path`',
},
],
}),
],
});