Skip to content

Commit

Permalink
[New] newline-after-import: new option exactCount and docs update
Browse files Browse the repository at this point in the history
Fixes #1901. Fixes #514.
  • Loading branch information
anikethsaha authored and ljharb committed Oct 27, 2020
1 parent 7c1e8e4 commit bb84371
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
- [`no-unused-modules`]: Support destructuring assignment for `export`. ([#1997], thanks [@s-h-a-d-o-w])
- [`order`]: support type imports ([#2021], thanks [@grit96])
- [`order`]: Add `warnOnUnassignedImports` option to enable warnings for out of order unassigned imports ([#1990], thanks [@hayes])
- [`newline-after-import`]: new option `exactCount` and docs update ([#1933], thanks [@anikethsaha])

### Fixed
- [`export`]/TypeScript: properly detect export specifiers as children of a TS module block ([#1889], thanks [@andreubotella])
Expand Down
95 changes: 73 additions & 22 deletions docs/rules/newline-after-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,123 @@ Enforces having one or more empty lines after the last top-level import statemen

## Rule Details

This rule has one option, `count` which sets the number of newlines that are enforced after the last top-level import statement or require call. This option defaults to `1`.
This rule accepts two options,

1. `count` which sets the number of newlines that are enforced after the last top-level import statement or require call. The number of newlines should be greater than or equal to `count`. This option defaults to `1`.

2. `exactCount` which enforce the exact numbers of newlines that is mentioned in `count`. This option defaults to `false`.


Valid:

```js
import defaultExport from './foo'
import defaultExport from './foo';

const FOO = 'BAR'
const FOO = 'BAR';
```

```js
import defaultExport from './foo'
import { bar } from 'bar-lib'
import defaultExport from './foo';
import { bar } from 'bar-lib';

const FOO = 'BAR'
const FOO = 'BAR';
```

```js
const FOO = require('./foo')
const BAR = require('./bar')
const FOO = require('./foo');
const BAR = require('./bar');

const BAZ = 1
const BAZ = 1;
```

Invalid:

```js
import * as foo from 'foo'
const FOO = 'BAR'
const FOO = 'BAR';
```

```js
import * as foo from 'foo'
const FOO = 'BAR'
import * as foo from 'foo';
const FOO = 'BAR';

import { bar } from 'bar-lib'
import { bar } from 'bar-lib';
```

```js
const FOO = require('./foo')
const BAZ = 1
const BAR = require('./bar')
const FOO = require('./foo');
const BAZ = 1;
const BAR = require('./bar');
```

With `count` set to `2` this will be considered valid:

```js
import defaultExport from './foo'
import defaultExport from './foo';


const FOO = 'BAR';
```

```js
import defaultExport from './foo';


const FOO = 'BAR'

const FOO = 'BAR';
```

With `count` set to `2` these will be considered invalid:

```js
import defaultExport from './foo'
const FOO = 'BAR'
import defaultExport from './foo';
const FOO = 'BAR';
```

```js
import defaultExport from './foo';

const FOO = 'BAR';
```

With `count` set to `2` and `exactCount` set to `true` this will be considered valid:

```js
import defaultExport from './foo';


const FOO = 'BAR';
```

With `count` set to `2` and `exactCount` set to `true` these will be considered invalid:

```js
import defaultExport from './foo';
const FOO = 'BAR';
```

```js
import defaultExport from './foo';

const FOO = 'BAR';
```

```js
import defaultExport from './foo'
import defaultExport from './foo';



const FOO = 'BAR'
const FOO = 'BAR';
```

```js
import defaultExport from './foo';




const FOO = 'BAR';
```

## Example options usage
```json
Expand Down
44 changes: 29 additions & 15 deletions src/rules/newline-after-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,18 @@ module.exports = {
fixable: 'whitespace',
schema: [
{
'type': 'object',
'properties': {
'count': {
'type': 'integer',
'minimum': 1,
type: 'object',
properties: {
count: {
type: 'integer',
minimum: 1,
},
exactCount: {
type: 'boolean',
default: false,
},
},
'additionalProperties': false,
additionalProperties: false,
},
],
},
Expand All @@ -86,11 +90,14 @@ module.exports = {
nextNode = nextNode.decorators[0];
}

const options = context.options[0] || { count: 1 };
const options = context.options[0] || { count: 1, exactCount: false };
const lineDifference = getLineDifference(node, nextNode);
const EXPECTED_LINE_DIFFERENCE = options.count + 1;

if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
if (
lineDifference < EXPECTED_LINE_DIFFERENCE
|| options.exactCount && lineDifference !== EXPECTED_LINE_DIFFERENCE
) {
let column = node.loc.start.column;

if (node.loc.start.line !== node.loc.end.line) {
Expand All @@ -104,10 +111,15 @@ module.exports = {
},
message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} \
after ${type} statement not followed by another ${type}.`,
fix: fixer => fixer.insertTextAfter(
node,
'\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)
),
fix: fixer => {
if (options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference) {
return null;
}
return fixer.insertTextAfter(
node,
'\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)
);
},
});
}
}
Expand Down Expand Up @@ -137,7 +149,7 @@ after ${type} statement not followed by another ${type}.`,
return {
ImportDeclaration: checkImport,
TSImportEqualsDeclaration: checkImport,
CallExpression: function(node) {
CallExpression: function (node) {
if (isStaticRequire(node) && level === 0) {
requireCalls.push(node);
}
Expand All @@ -159,8 +171,10 @@ after ${type} statement not followed by another ${type}.`,
return;
}

if (nextStatement &&
(!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) {
if (
nextStatement
&& (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))
) {

checkForNewLine(statementWithRequireCall, nextStatement, 'require');
}
Expand Down
75 changes: 75 additions & 0 deletions tests/src/rules/newline-after-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 2 }],
},
{
code: `import foo from 'foo';\n\n\nvar bar = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 2, 'exactCount': true }],
},
{
code: `import foo from 'foo';\n\nvar bar = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 1, 'exactCount': true }],
},
{
code: `import foo from 'foo';\n\n\nvar bar = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 1 }],
},
{
code: `import foo from 'foo';\n\n\n\n\nvar bar = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
Expand All @@ -110,6 +125,11 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 4 }],
},
{
code: `var foo = require('foo-module');\n\n\n\n\nvar foo = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
options: [{ 'count': 4, 'exactCount' : true }],
},
{
code: `require('foo-module');\n\nvar foo = 'bar';`,
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
Expand Down Expand Up @@ -453,5 +473,60 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
parserOptions: { sourceType: 'module' },
parser: require.resolve('babel-eslint'),
})) || [],
{
code: `import foo from 'foo';\n\nexport default function() {};`,
output: `import foo from 'foo';\n\n\nexport default function() {};`,
options: [{ 'count': 2, exactCount: true }],
errors: [ {
line: 1,
column: 1,
message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
} ],
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
},
{
code: `import foo from 'foo';\n\n\n\nexport default function() {};`,
output: null,
options: [{ 'count': 2, exactCount: true }],
errors: [ {
line: 1,
column: 1,
message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
} ],
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
},
{
code: `import foo from 'foo';\n\n\n\n\nexport default function() {};`,
output: null,
options: [{ 'count': 2, exactCount: true }],
errors: [ {
line: 1,
column: 1,
message: IMPORT_ERROR_MESSAGE_MULTIPLE(2),
} ],
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
},
{
code: `import foo from 'foo';export default function() {};`,
output: `import foo from 'foo';\n\nexport default function() {};`,
options: [{ 'count': 1, exactCount: true }],
errors: [ {
line: 1,
column: 1,
message: IMPORT_ERROR_MESSAGE,
} ],
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
},
{
code: `const foo = require('foo');\n\n\n\nconst bar = function() {};`,
output: null,
options: [{ 'count': 2, exactCount: true }],
errors: [ {
line: 1,
column: 1,
message: 'Expected 2 empty lines after require statement not followed by another require.',
} ],
parserOptions: { ecmaVersion: 2015 },
},
),
});

0 comments on commit bb84371

Please sign in to comment.