diff --git a/CHANGELOG.md b/CHANGELOG.md index cd7a4380a..567ed2645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added - TypeScript config: add .cts and .mts extensions ([#2851], thanks [@Zamiell]) +- [`newline-after-import`]: new option `exactCount` and docs update ([#1933], thanks [@anikethsaha] and [@reosarevok]) ## [2.28.1] - 2023-08-18 @@ -1901,4 +1902,4 @@ for info on changes for earlier releases. [@yndajas]: https://github.com/yndajas [@yordis]: https://github.com/yordis [@Zamiell]: https://github.com/Zamiell -[@zloirock]: https://github.com/zloirock \ No newline at end of file +[@zloirock]: https://github.com/zloirock diff --git a/docs/rules/newline-after-import.md b/docs/rules/newline-after-import.md index fec6b23ac..ef5aeed76 100644 --- a/docs/rules/newline-after-import.md +++ b/docs/rules/newline-after-import.md @@ -12,70 +12,119 @@ This rule supports the following options: - `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`. + - `exactCount` which enforce the exact numbers of newlines that is mentioned in `count`. This option defaults to `false`. + - `considerComments` which enforces the rule on comments after the last import-statement as well when set to true. 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' +const FOO = 'BAR'; +``` + +```js +import defaultExport from './foo'; + + + +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' +import defaultExport from './foo'; -const FOO = 'BAR' +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'; + + + +const FOO = 'BAR'; +``` + +```js +import defaultExport from './foo'; + + + + +const FOO = 'BAR'; ``` With `considerComments` set to `false` this will be considered valid: diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 8855e26e5..9b469110b 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -69,6 +69,7 @@ module.exports = { type: 'integer', minimum: 1, }, + exactCount: { type: 'boolean' }, considerComments: { type: 'boolean' }, }, additionalProperties: false, @@ -78,7 +79,12 @@ module.exports = { create(context) { let level = 0; const requireCalls = []; - const options = { count: 1, considerComments: false, ...context.options[0] }; + const options = { + count: 1, + exactCount: false, + considerComments: false, + ...context.options[0], + }; function checkForNewLine(node, nextNode, type) { if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) { @@ -94,7 +100,10 @@ module.exports = { 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) { @@ -107,7 +116,7 @@ module.exports = { column, }, message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} after ${type} statement not followed by another ${type}.`, - fix: (fixer) => fixer.insertTextAfter( + fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? undefined : (fixer) => fixer.insertTextAfter( node, '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference), ), @@ -132,7 +141,7 @@ module.exports = { column, }, message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} after import statement not followed by another import.`, - fix: (fixer) => fixer.insertTextAfter( + fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? undefined : (fixer) => fixer.insertTextAfter( node, '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference), ), diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 8f3338eee..bd11da419 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -46,6 +46,16 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { ecmaVersion: 6 }, options: [{ considerComments: true }], }, + { + code: ` + const x = () => require('baz') && require('bar') + + // Some random single line comment + var bar = 42; + `, + parserOptions: { ecmaVersion: 6 }, + options: [{ considerComments: true, count: 1, exactCount: true }], + }, { code: ` const x = () => require('baz') && require('bar') @@ -122,6 +132,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' }, @@ -141,6 +166,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' }, @@ -620,5 +650,83 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { sourceType: 'module' }, parser: parsers.BABEL_OLD, })) || [], + { + 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: `import foo from 'foo';\n\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\n\nexport default function() {};`, + output: `import foo from 'foo';\n\n\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'; + + + // Some random single line comment + var bar = 42; + `, + output: ` + import foo from 'foo'; + + + // Some random single line comment + var bar = 42; + `, + errors: [{ + line: 2, + column: 9, + message: IMPORT_ERROR_MESSAGE, + }], + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + options: [{ considerComments: true, count: 1, exactCount: true }], + }, + { + 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: `const foo = require('foo');\n\n\n\nconst bar = function() {};`, + 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 }, + }, ), });