From c5a094470b9984e1a83ffd740459cbaab739673d Mon Sep 17 00:00:00 2001 From: "Xunnamius (Romulus)" Date: Mon, 18 Nov 2024 03:14:00 -0800 Subject: [PATCH] feat(import/order): enable advanced spacing and sorting of type-only imports --- src/rules/order.js | 210 ++- tests/src/rules/order.js | 3028 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 3049 insertions(+), 189 deletions(-) diff --git a/src/rules/order.js b/src/rules/order.js index d6f25ddd3..524bfe892 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -513,22 +513,38 @@ function computePathRank(ranks, pathGroups, path, maxPosition) { } } -function computeRank(context, ranks, importEntry, excludedImportTypes) { +function computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves) { let impType; let rank; + + const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1; + const isTypeOnlyImport = importEntry.node.importKind === 'type'; + const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes.has('type') + if (importEntry.type === 'import:object') { impType = 'object'; - } else if (importEntry.node.importKind === 'type' && ranks.omittedTypes.indexOf('type') === -1) { + } else if (isTypeOnlyImport && isTypeGroupInGroups && !isSortingTypesAmongThemselves) { impType = 'type'; } else { impType = importType(importEntry.value, context); } - if (!excludedImportTypes.has(impType)) { + + if (!excludedImportTypes.has(impType) && !isExcludedFromPathRank) { rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition); } - if (typeof rank === 'undefined') { + + if (rank === undefined) { rank = ranks.groups[impType]; + + if(rank === undefined) { + return -1; + } } + + if (isTypeOnlyImport && isSortingTypesAmongThemselves) { + rank = ranks.groups['type'] + rank / 10; + } + if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) { rank += 100; } @@ -536,10 +552,20 @@ function computeRank(context, ranks, importEntry, excludedImportTypes) { return rank; } -function registerNode(context, importEntry, ranks, imported, excludedImportTypes) { - const rank = computeRank(context, ranks, importEntry, excludedImportTypes); +function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesAmongThemselves) { + const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves); if (rank !== -1) { - imported.push({ ...importEntry, rank }); + let importNode = importEntry.node; + + if(importEntry.type === 'require' && importNode.parent.parent.type === 'VariableDeclaration') { + importNode = importNode.parent.parent; + } + + imported.push({ + ...importEntry, + rank, + isMultiline: importNode.loc.end.line !== importNode.loc.start.line + }); } } @@ -665,7 +691,7 @@ function removeNewLineAfterImport(context, currentImport, previousImport) { return undefined; } -function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup) { +function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, newlinesBetweenTypeOnlyImports_, distinctGroup, isSortingTypesAmongThemselves, isConsolidatingSpaceBetweenImports) { const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => { const linesBetweenImports = getSourceCode(context).lines.slice( previousImport.node.loc.end.line, @@ -678,35 +704,124 @@ function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, di let previousImport = imported[0]; imported.slice(1).forEach(function (currentImport) { - const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport); - const isStartOfDistinctGroup = getIsStartOfDistinctGroup(currentImport, previousImport); + const emptyLinesBetween = getNumberOfEmptyLinesBetween( + currentImport, + previousImport + ); + + const isStartOfDistinctGroup = getIsStartOfDistinctGroup( + currentImport, + previousImport + ); - if (newlinesBetweenImports === 'always' - || newlinesBetweenImports === 'always-and-inside-groups') { - if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) { - if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) { + const isTypeOnlyImport = currentImport.node.importKind === 'type'; + const isPreviousImportTypeOnlyImport = previousImport.node.importKind === 'type'; + + const isNormalImportNextToTypeOnlyImportAndRelevant = + isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesAmongThemselves; + + const isTypeOnlyImportAndRelevant = + isTypeOnlyImport && isSortingTypesAmongThemselves; + + // In the special case where newlinesBetweenTypeOnlyImports and + // consolidateIslands want the opposite thing, consolidateIslands wins + const newlinesBetweenTypeOnlyImports = + newlinesBetweenTypeOnlyImports_ === 'never' && + isConsolidatingSpaceBetweenImports && + isSortingTypesAmongThemselves && + (isNormalImportNextToTypeOnlyImportAndRelevant || + previousImport.isMultiline || + currentImport.isMultiline) + ? 'always-and-inside-groups' + : newlinesBetweenTypeOnlyImports_; + + const isNotIgnored = + (isTypeOnlyImportAndRelevant && + newlinesBetweenTypeOnlyImports !== 'ignore') || + (!isTypeOnlyImportAndRelevant && newlinesBetweenImports !== 'ignore'); + + if(isNotIgnored) { + const shouldAssertNewlineBetweenGroups = + ((isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) && + (newlinesBetweenTypeOnlyImports === 'always' || + newlinesBetweenTypeOnlyImports === 'always-and-inside-groups')) || + ((!isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant) && + (newlinesBetweenImports === 'always' || + newlinesBetweenImports === 'always-and-inside-groups')); + + const shouldAssertNoNewlineWithinGroup = + ((isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) && + (newlinesBetweenTypeOnlyImports !== 'always-and-inside-groups')) || + ((!isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant) && + (newlinesBetweenImports !== 'always-and-inside-groups')); + + const shouldAssertNoNewlineBetweenGroup = + !isSortingTypesAmongThemselves || + !isNormalImportNextToTypeOnlyImportAndRelevant || + newlinesBetweenTypeOnlyImports === 'never'; + + const isTheNewlineBetweenImportsInTheSameGroup = (distinctGroup && currentImport.rank === previousImport.rank) || + (!distinctGroup && !isStartOfDistinctGroup); + + // Let's try to cut down on linting errors sent to the user + let alreadyReported = false; + + if (shouldAssertNewlineBetweenGroups) { + if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) { + if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) { + alreadyReported = true; + context.report({ + node: previousImport.node, + message: 'There should be at least one empty line between import groups', + fix: fixNewLineAfterImport(context, previousImport), + }); + } + } else if (emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup) { + if (isTheNewlineBetweenImportsInTheSameGroup) { + alreadyReported = true; + context.report({ + node: previousImport.node, + message: 'There should be no empty line within import group', + fix: removeNewLineAfterImport(context, currentImport, previousImport) + }); + } + } + } else if (emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup) { + alreadyReported = true; + context.report({ + node: previousImport.node, + message: 'There should be no empty line between import groups', + fix: removeNewLineAfterImport(context, currentImport, previousImport), + }); + } + + if(!alreadyReported && isConsolidatingSpaceBetweenImports) { + if(emptyLinesBetween === 0 && currentImport.isMultiline) { context.report({ node: previousImport.node, - message: 'There should be at least one empty line between import groups', + message: 'There should be at least one empty line between this import and the multi-line import that follows it', fix: fixNewLineAfterImport(context, previousImport), }); - } - } else if (emptyLinesBetween > 0 - && newlinesBetweenImports !== 'always-and-inside-groups') { - if (distinctGroup && currentImport.rank === previousImport.rank || !distinctGroup && !isStartOfDistinctGroup) { + } else if(emptyLinesBetween === 0 && previousImport.isMultiline) { context.report({ node: previousImport.node, - message: 'There should be no empty line within import group', - fix: removeNewLineAfterImport(context, currentImport, previousImport), + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + fix: fixNewLineAfterImport(context, previousImport), + }); + } else if ( + emptyLinesBetween > 0 && + !previousImport.isMultiline && + !currentImport.isMultiline && + isTheNewlineBetweenImportsInTheSameGroup + ) { + context.report({ + node: previousImport.node, + message: + 'There should be no empty lines between this single-line import and the single-line import that follows it', + fix: removeNewLineAfterImport(context, currentImport, previousImport) }); } } - } else if (emptyLinesBetween > 0) { - context.report({ - node: previousImport.node, - message: 'There should be no empty line between import groups', - fix: removeNewLineAfterImport(context, currentImport, previousImport), - }); } previousImport = currentImport; @@ -781,6 +896,24 @@ module.exports = { 'never', ], }, + 'newlines-between-types': { + enum: [ + 'ignore', + 'always', + 'always-and-inside-groups', + 'never', + ], + }, + consolidateIslands: { + enum: [ + 'inside-groups', + 'never', + ], + }, + sortTypesAmongThemselves: { + type: 'boolean', + default: false, + }, named: { default: false, oneOf: [{ @@ -836,7 +969,10 @@ module.exports = { create(context) { const options = context.options[0] || {}; const newlinesBetweenImports = options['newlines-between'] || 'ignore'; + const newlinesBetweenTypeOnlyImports = options['newlines-between-types'] || newlinesBetweenImports; const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']); + const sortTypesAmongThemselves = options.sortTypesAmongThemselves; + const consolidateIslands = options.consolidateIslands || 'never'; const named = { types: 'mixed', @@ -879,6 +1015,9 @@ module.exports = { const importMap = new Map(); const exportMap = new Map(); + const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1; + const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves; + function getBlockImports(node) { if (!importMap.has(node)) { importMap.set(node, []); @@ -932,6 +1071,7 @@ module.exports = { ranks, getBlockImports(node.parent), pathGroupsExcludedImportTypes, + isSortingTypesAmongThemselves ); if (named.import) { @@ -983,6 +1123,7 @@ module.exports = { ranks, getBlockImports(node.parent), pathGroupsExcludedImportTypes, + isSortingTypesAmongThemselves ); }, CallExpression(node) { @@ -1005,6 +1146,7 @@ module.exports = { ranks, getBlockImports(block), pathGroupsExcludedImportTypes, + isSortingTypesAmongThemselves ); }, ...named.require && { @@ -1092,8 +1234,18 @@ module.exports = { }, 'Program:exit'() { importMap.forEach((imported) => { - if (newlinesBetweenImports !== 'ignore') { - makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup); + if (newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore') { + makeNewlinesBetweenReport( + context, + imported, + newlinesBetweenImports, + newlinesBetweenTypeOnlyImports, + distinctGroup, + isSortingTypesAmongThemselves, + consolidateIslands === 'inside-groups' && + (newlinesBetweenImports === 'always-and-inside-groups' || + newlinesBetweenTypeOnlyImports === 'always-and-inside-groups') + ); } if (alphabetize.order !== 'ignore') { diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index ea62cec71..0e9696269 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -530,7 +530,7 @@ ruleTester.run('order', rule, { }, ], }), - // Option newlines-between: 'always' with multiline imports #1 + // Option newlines-between: 'always' with multi-line imports #1 test({ code: ` import path from 'path'; @@ -546,7 +546,7 @@ ruleTester.run('order', rule, { `, options: [{ 'newlines-between': 'always' }], }), - // Option newlines-between: 'always' with multiline imports #2 + // Option newlines-between: 'always' with multi-line imports #2 test({ code: ` import path from 'path'; @@ -557,7 +557,7 @@ ruleTester.run('order', rule, { `, options: [{ 'newlines-between': 'always' }], }), - // Option newlines-between: 'always' with multiline imports #3 + // Option newlines-between: 'always' with multi-line imports #3 test({ code: ` import foo @@ -671,6 +671,33 @@ ruleTester.run('order', rule, { }, ], }), + // Option newlines-between: 'always-and-inside-groups' and consolidateIslands: true + test({ + code: ` + var fs = require('fs'); + var path = require('path'); + var util = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent2 } = require('../'); + + var relParent3 = require('../bar'); + + var sibling = require('./foo'); + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); + `, + options: [ + { + 'newlines-between': 'always-and-inside-groups', + consolidateIslands: 'inside-groups' + }, + ], + }), // Option alphabetize: {order: 'ignore'} test({ code: ` @@ -3032,6 +3059,156 @@ ruleTester.run('order', rule, { }], }), ], + // Option newlines-between: 'always-and-inside-groups' and consolidateIslands: true + test({ + code: ` + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + var async = require('async'); + var relParent1 = require('../foo'); + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + var relParent3 = require('../bar'); + var { sibling1, + sibling2, sibling3 } = require('./foo'); + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); + `, + output: ` + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + + var relParent3 = require('../bar'); + + var { sibling1, + sibling2, sibling3 } = require('./foo'); + + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); + `, + options: [ + { + 'newlines-between': 'always-and-inside-groups', + consolidateIslands: 'inside-groups' + }, + ], + errors: [ + { + message: 'There should be at least one empty line between import groups', + line: 4, + }, + { + message: 'There should be at least one empty line between import groups', + line: 5, + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 6, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 12, + }, + { + message: 'There should be at least one empty line between import groups', + line: 13, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 15, + }, + ], + }), + test({ + code: ` + var fs = require('fs'); + + var path = require('path'); + + var { util1, util2, util3 } = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + + var relParent3 = require('../bar'); + + var { sibling1, + sibling2, sibling3 } = require('./foo'); + + var sibling2 = require('./bar'); + + var sibling3 = require('./foobar'); + `, + output: ` + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + + var relParent3 = require('../bar'); + + var { sibling1, + sibling2, sibling3 } = require('./foo'); + + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); + `, + options: [ + { + 'newlines-between': 'always-and-inside-groups', + consolidateIslands: 'inside-groups' + }, + ], + errors: [ + { + message: 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 2, + }, + { + message: 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 4, + }, + { + message: 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 24, + }, + ], + }), ].filter(Boolean), }); @@ -3115,7 +3292,6 @@ context('TypeScript', function () { }), // Option alphabetize: {order: 'asc'} with type group & path group test({ - // only: true, code: ` import c from 'Bar'; import a from 'foo'; @@ -3145,7 +3321,6 @@ context('TypeScript', function () { }), // Option alphabetize: {order: 'asc'} with path group test({ - // only: true, code: ` import c from 'Bar'; import type { A } from 'foo'; @@ -3285,258 +3460,2791 @@ context('TypeScript', function () { }], }), ] : [], - ), - invalid: [].concat( - // Option alphabetize: {order: 'asc'} + // Option sortTypesAmongThemselves: false (default) test({ code: ` - import b from 'bar'; import c from 'Bar'; - import type { C } from 'Bar'; import a from 'foo'; - import type { A } from 'foo'; - import index from './'; - `, - output: ` - import c from 'Bar'; - import type { C } from 'Bar'; - import b from 'bar'; - import a from 'foo'; - import type { A } from 'foo'; + import type { C } from 'dirA/Bar'; + import b from 'dirA/bar'; + import type { D } from 'dirA/bar'; import index from './'; + + import type { AA } from 'abc'; + import type { A } from 'foo'; `, ...parserConfig, options: [ { - groups: ['external', 'index'], alphabetize: { order: 'asc' }, - }, - ], - errors: [ - { - message: semver.satisfies(eslintPkg.version, '< 3') - ? '`bar` import should occur after type import of `Bar`' - : /(`bar` import should occur after type import of `Bar`)|(`Bar` type import should occur before import of `bar`)/, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], }, ], }), - // Option alphabetize: {order: 'desc'} test({ code: ` - import a from 'foo'; - import type { A } from 'foo'; import c from 'Bar'; - import type { C } from 'Bar'; - import b from 'bar'; - - import index from './'; - `, - output: ` import a from 'foo'; - import type { A } from 'foo'; - import b from 'bar'; - import c from 'Bar'; - import type { C } from 'Bar'; + + import type { C } from 'dirA/Bar'; + import b from 'dirA/bar'; + import type { D } from 'dirA/bar'; import index from './'; + + import type { AA } from 'abc'; + import type { A } from 'foo'; `, ...parserConfig, options: [ { - groups: ['external', 'index'], - alphabetize: { order: 'desc' }, - }, - ], - errors: [ - { - message: semver.satisfies(eslintPkg.version, '< 3') - ? '`bar` import should occur before import of `Bar`' - : /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/, + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: false, }, ], }), - // Option alphabetize: {order: 'asc'} with type group + // Option sortTypesAmongThemselves: true and 'type' in pathGroupsExcludedImportTypes test({ code: ` - import b from 'bar'; import c from 'Bar'; import a from 'foo'; - import index from './'; - - import type { A } from 'foo'; - import type { C } from 'Bar'; - `, - output: ` - import c from 'Bar'; - import b from 'bar'; - import a from 'foo'; + import b from 'dirA/bar'; import index from './'; - import type { C } from 'Bar'; + import type { AA } from 'abc'; + import type { C } from 'dirA/Bar'; + import type { D } from 'dirA/bar'; import type { A } from 'foo'; `, ...parserConfig, options: [ { - groups: ['external', 'index', 'type'], alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: ['type'], + sortTypesAmongThemselves: true, }, ], - errors: semver.satisfies(eslintPkg.version, '< 3') ? [ - { message: '`Bar` import should occur before import of `bar`' }, - { message: '`Bar` type import should occur before type import of `foo`' }, - ] : [ - { message: /(`Bar` import should occur before import of `bar`)|(`bar` import should occur after import of `Bar`)/ }, - { message: /(`Bar` type import should occur before type import of `foo`)|(`foo` type import should occur after type import of `Bar`)/ }, - ], }), - // Option alphabetize: {order: 'desc'} with type group + // Option sortTypesAmongThemselves: true and 'type' omitted from groups test({ code: ` - import a from 'foo'; import c from 'Bar'; - import b from 'bar'; - - import index from './'; - - import type { C } from 'Bar'; - import type { A } from 'foo'; - `, - output: ` + import type { AA } from 'abc'; import a from 'foo'; - import b from 'bar'; - import c from 'Bar'; + import type { A } from 'foo'; - import index from './'; + import type { C } from 'dirA/Bar'; + import b from 'dirA/bar'; + import type { D } from 'dirA/bar'; - import type { A } from 'foo'; - import type { C } from 'Bar'; + import index from './'; `, ...parserConfig, options: [ { - groups: ['external', 'index', 'type'], - alphabetize: { order: 'desc' }, - }, - ], - errors: semver.satisfies(eslintPkg.version, '< 3') ? [ - { message: '`bar` import should occur before import of `Bar`' }, - { message: '`foo` type import should occur before type import of `Bar`' }, - ] : [ - { message: /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/ }, - { message: /(`foo` type import should occur before type import of `Bar`)|(`Bar` type import should occur after import of type `foo`)/ }, + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], + // Becomes a no-op without "type" in groups + sortTypesAmongThemselves: true, + }, + ], + }), + test({ + code: ` + import c from 'Bar'; + import type { AA } from 'abc'; + import a from 'foo'; + import type { A } from 'foo'; + + import type { C } from 'dirA/Bar'; + import b from 'dirA/bar'; + import type { D } from 'dirA/bar'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], + }, + ], + }), + // Option sortTypesAmongThemselves: true and newlines-between-types defaults to the value of newlines-between + test({ + code: ` + import c from 'Bar'; + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA } from 'abc'; + import type { A } from 'foo'; + + import type { C } from 'dirA/Bar'; + import type { D } from 'dirA/bar'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always' (takes precedence over newlines-between between type-only and normal imports) + test({ + code: ` + import c from 'Bar'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + + import type { AA } from 'abc'; + import type { A } from 'foo'; + + import type { C } from 'dirA/Bar'; + import type { D } from 'dirA/bar'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'never', + 'newlines-between-types': 'always', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'never' (takes precedence over newlines-between between type-only and normal imports) + test({ + code: ` + import c from 'Bar'; + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + import type { AA } from 'abc'; + import type { A } from 'foo'; + import type { C } from 'dirA/Bar'; + import type { D } from 'dirA/bar'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'always', + 'newlines-between-types': 'never', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'ignore' + test({ + code: ` + import c from 'Bar'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + import type { AA } from 'abc'; + + import type { A } from 'foo'; + import type { C } from 'dirA/Bar'; + import type { D } from 'dirA/bar'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'never', + 'newlines-between-types': 'ignore', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' + test({ + code: ` + import c from 'Bar'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + + import type { AA } from 'abc'; + + import type { A } from 'foo'; + + import type { C } from 'dirA/Bar'; + + import type { D } from 'dirA/bar'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + }, + ], + 'newlines-between': 'never', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true puts type imports in the same order as regular imports (from issue #2441, PR #2615) + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + `, + ...parserConfig, + options: [ + { + groups: ['type', 'builtin', 'parent', 'sibling', 'index'], + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + sortTypesAmongThemselves: true, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups' + test({ + code: ` + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + position: 'after' + }, + { + pattern: 'dirB/**', + group: 'internal', + position: 'before' + }, + { + pattern: 'dirC/**', + group: 'internal', + }, + ], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + consolidateIslands: 'inside-groups', + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'never' (default) + test({ + code: ` + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + position: 'after' + }, + { + pattern: 'dirB/**', + group: 'internal', + position: 'before' + }, + { + pattern: 'dirC/**', + group: 'internal', + }, + ], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + consolidateIslands: 'never', + }, + ], + }), + // Ensure the rule doesn't choke and die on absolute paths trying to pass NaN around + test({ + code: ` + import fs from 'node:fs'; + + import '@scoped/package'; + import type { B } from 'node:fs'; + + import type { A1 } from '/bad/bad/bad/bad'; + import './a/b/c'; + import type { A2 } from '/bad/bad/bad/bad'; + import type { A3 } from '/bad/bad/bad/bad'; + import type { D1 } from '/bad/bad/not/good'; + import type { D2 } from '/bad/bad/not/good'; + import type { D3 } from '/bad/bad/not/good'; + + import type { C } from '@something/else'; + + import type { E } from './index.js'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['builtin', 'type', 'unknown', 'external'], + sortTypesAmongThemselves: true, + 'newlines-between': 'always' + }, + ], + }), + // Ensure consolidateOptions: 'inside-groups', newlines-between: 'always-and-inside-groups', and newlines-between-types: 'never' do not fight for dominance + test({ + code: ` + import makeVanillaYargs from 'yargs/yargs'; + + import { createDebugLogger } from 'multiverse+rejoinder'; + + import { globalDebuggerNamespace } from 'rootverse+bfe:src/constant.ts'; + import { ErrorMessage, type KeyValueEntry } from 'rootverse+bfe:src/error.ts'; + + import { + $artificiallyInvoked, + $canonical, + $exists, + $genesis + } from 'rootverse+bfe:src/symbols.ts'; + + import type { + Entries, + LiteralUnion, + OmitIndexSignature, + Promisable, + StringKeyOf + } from 'type-fest'; + `, + ...parserConfig, + options: [ + { + alphabetize: { + order: 'asc', + orderImportKind: 'asc', + caseInsensitive: true + }, + named: { + enabled: true, + types: 'types-last' + }, + groups: [ + 'builtin', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + ['object', 'type'] + ], + pathGroups: [ + { + pattern: 'multiverse{*,*/**}', + group: 'external', + position: 'after' + }, + { + pattern: 'rootverse{*,*/**}', + group: 'external', + position: 'after' + }, + { + pattern: 'universe{*,*/**}', + group: 'external', + position: 'after' + }, + ], + distinctGroup: true, + pathGroupsExcludedImportTypes: ['builtin', 'object'], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'never', + sortTypesAmongThemselves: true, + consolidateIslands: 'inside-groups' + }, + ], + }), + test({ + code: ` + import assert from 'node:assert'; + import { isNativeError } from 'node:util/types'; + + import { runNoRejectOnBadExit } from '@-xun/run'; + import { TrialError } from 'named-app-errors'; + import { resolve as resolverLibrary } from 'resolve.exports'; + + import { toAbsolutePath, type AbsolutePath } from 'rootverse+project-utils:src/fs.ts'; + + import type { PackageJson } from 'type-fest'; + // Some comment about remembering to do something + import type { XPackageJson } from 'rootverse:src/assets/config/_package.json.ts'; + `, + ...parserConfig, + options: [ + { + alphabetize: { + order: 'asc', + orderImportKind: 'asc', + caseInsensitive: true + }, + named: { + enabled: true, + types: 'types-last' + }, + groups: [ + 'builtin', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + ['object', 'type'] + ], + pathGroups: [ + { + pattern: 'rootverse{*,*/**}', + group: 'external', + position: 'after' + }, + ], + distinctGroup: true, + pathGroupsExcludedImportTypes: ['builtin', 'object'], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'never', + sortTypesAmongThemselves: true, + consolidateIslands: 'inside-groups' + }, + ], + }), + + // Documentation passing example #1 for newlines-between + test({ + code: ` + import fs from 'fs'; + import path from 'path'; + + import sibling from './foo'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'always' + } + ], + }), + // Documentation passing example #2 for newlines-between + test({ + code: ` + import fs from 'fs'; + + import path from 'path'; + + import sibling from './foo'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'always-and-inside-groups' + } + ], + }), + // Documentation passing example #3 for newlines-between + test({ + code: ` + import fs from 'fs'; + import path from 'path'; + import sibling from './foo'; + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'never' + } + ], + }), + // Documentation passing example #1 for alphabetize + test({ + code: ` + import blist2 from 'blist'; + import blist from 'BList'; + import * as classnames from 'classnames'; + import aTypes from 'prop-types'; + import React, { PureComponent } from 'react'; + import { compose, apply } from 'xcompose'; + `, + ...parserConfig, + options: [ + { + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], + }), + // (not an example, but we also test caseInsensitive: false for completeness) + test({ + code: ` + import blist from 'BList'; + import blist2 from 'blist'; + import * as classnames from 'classnames'; + import aTypes from 'prop-types'; + import React, { PureComponent } from 'react'; + import { compose, apply } from 'xcompose'; + `, + ...parserConfig, + options: [ + { + "alphabetize": { + "order": "asc", + "caseInsensitive": false + } + } + ], + }), + // Documentation passing example #1 for named + test({ + code: ` + import { apply, compose } from 'xcompose'; + `, + ...parserConfig, + options: [ + { + "named": true, + "alphabetize": { + "order": "asc" + } + } + ], + }), + // Documentation passing example #1 for warnOnUnassignedImports + test({ + code: ` + import fs from 'fs'; + import path from 'path'; + import './styles.css'; + `, + ...parserConfig, + options: [ + { + "warnOnUnassignedImports": true + } + ], + }), + // Documentation passing example #1 for sortTypesAmongThemselves + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + `, + ...parserConfig, + options: [ + { + "groups": ["type", "builtin", "parent", "sibling", "index"], + "alphabetize": { "order": "asc" }, + "sortTypesAmongThemselves": true + } + ], + }), + // (not an example, but we also test the reverse for completeness) + test({ + code: ` + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + `, + ...parserConfig, + options: [ + { + "groups": ["builtin", "parent", "sibling", "index", "type"], + "sortTypesAmongThemselves": true + } + ], + }), + // Documentation passing example #1 for newlines-between-types + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; + `, + ...parserConfig, + options: [ + { + groups: ['type', 'builtin', 'parent', 'sibling', 'index'], + sortTypesAmongThemselves: true, + 'newlines-between': 'always', + 'newlines-between-types': 'ignore' + } + ], + }), + // (not an example, but we also test the reverse for completeness) + test({ + code: ` + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; + + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + `, + ...parserConfig, + options: [ + { + groups: ['builtin', 'parent', 'sibling', 'index', 'type'], + sortTypesAmongThemselves: true, + 'newlines-between': 'always', + 'newlines-between-types': 'ignore' + } + ], + }), + // Documentation passing example #2 for newlines-between-types + test({ + code: ` + import type A from "fs"; + import type B from "path"; + + import type C from "../foo.js"; + + import type D from "./bar.js"; + + import type E from './'; + + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + `, + ...parserConfig, + options: [ + { + groups: ['type', 'builtin', 'parent', 'sibling', 'index'], + sortTypesAmongThemselves: true, + 'newlines-between': 'never', + 'newlines-between-types': 'always' + } + ], + }), + // (not an example, but we also test the reverse for completeness) + test({ + code: ` + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + + import type A from "fs"; + import type B from "path"; + + import type C from "../foo.js"; + + import type D from "./bar.js"; + + import type E from './'; + `, + ...parserConfig, + options: [ + { + groups: ['builtin', 'parent', 'sibling', 'index', 'type'], + sortTypesAmongThemselves: true, + 'newlines-between': 'never', + 'newlines-between-types': 'always' + } + ], + }), + // Documentation passing example #1 for consolidateIslands + test({ + code: ` + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + + var relParent3 = require('../bar'); + + var { sibling1, + sibling2, sibling3 } = require('./foo'); + + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); + `, + ...parserConfig, + options: [ + { + "newlines-between": "always-and-inside-groups", + "consolidateIslands": "inside-groups" + } + ], + }), + // Documentation passing example #2 for consolidateIslands + test({ + code: ` + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + "alphabetize": { "order": "asc" }, + "groups": ["external", "internal", "index", "type"], + "pathGroups": [ + { + "pattern": "dirA/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "dirB/**", + "group": "internal", + "position": "before" + }, + { + "pattern": "dirC/**", + "group": "internal" + } + ], + "newlines-between": "always-and-inside-groups", + "newlines-between-types": "never", + "pathGroupsExcludedImportTypes": [], + "sortTypesAmongThemselves": true, + "consolidateIslands": "inside-groups" + } + ], + }), + // (not an example, but we also test the reverse for completeness) + test({ + code: ` + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; + + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + "alphabetize": { "order": "asc" }, + "groups": ["type", "external", "internal", "index"], + "pathGroups": [ + { + "pattern": "dirA/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "dirB/**", + "group": "internal", + "position": "before" + }, + { + "pattern": "dirC/**", + "group": "internal" + } + ], + "newlines-between": "always-and-inside-groups", + "newlines-between-types": "never", + "pathGroupsExcludedImportTypes": [], + "sortTypesAmongThemselves": true, + "consolidateIslands": "inside-groups" + } + ], + }), + ), + invalid: [].concat( + // Option alphabetize: {order: 'asc'} + test({ + code: ` + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + import a from 'foo'; + import type { A } from 'foo'; + + import index from './'; + `, + output: ` + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + import a from 'foo'; + import type { A } from 'foo'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'asc' }, + }, + ], + errors: [ + { + message: semver.satisfies(eslintPkg.version, '< 3') + ? '`bar` import should occur after type import of `Bar`' + : /(`bar` import should occur after type import of `Bar`)|(`Bar` type import should occur before import of `bar`)/, + }, + ], + }), + // Option alphabetize: {order: 'desc'} + test({ + code: ` + import a from 'foo'; + import type { A } from 'foo'; + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + + import index from './'; + `, + output: ` + import a from 'foo'; + import type { A } from 'foo'; + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'desc' }, + }, + ], + errors: [ + { + message: semver.satisfies(eslintPkg.version, '< 3') + ? '`bar` import should occur before import of `Bar`' + : /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/, + }, + ], + }), + // Option alphabetize: {order: 'asc'} with type group + test({ + code: ` + import b from 'bar'; + import c from 'Bar'; + import a from 'foo'; + + import index from './'; + + import type { A } from 'foo'; + import type { C } from 'Bar'; + `, + output: ` + import c from 'Bar'; + import b from 'bar'; + import a from 'foo'; + + import index from './'; + + import type { C } from 'Bar'; + import type { A } from 'foo'; + `, + ...parserConfig, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'asc' }, + }, + ], + errors: semver.satisfies(eslintPkg.version, '< 3') ? [ + { message: '`Bar` import should occur before import of `bar`' }, + { message: '`Bar` type import should occur before type import of `foo`' }, + ] : [ + { message: /(`Bar` import should occur before import of `bar`)|(`bar` import should occur after import of `Bar`)/ }, + { message: /(`Bar` type import should occur before type import of `foo`)|(`foo` type import should occur after type import of `Bar`)/ }, + ], + }), + // Option alphabetize: {order: 'desc'} with type group + test({ + code: ` + import a from 'foo'; + import c from 'Bar'; + import b from 'bar'; + + import index from './'; + + import type { C } from 'Bar'; + import type { A } from 'foo'; + `, + output: ` + import a from 'foo'; + import b from 'bar'; + import c from 'Bar'; + + import index from './'; + + import type { A } from 'foo'; + import type { C } from 'Bar'; + `, + ...parserConfig, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'desc' }, + }, + ], + errors: semver.satisfies(eslintPkg.version, '< 3') ? [ + { message: '`bar` import should occur before import of `Bar`' }, + { message: '`foo` type import should occur before type import of `Bar`' }, + ] : [ + { message: /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/ }, + { message: /(`foo` type import should occur before type import of `Bar`)|(`Bar` type import should occur after import of type `foo`)/ }, + ], + }), + // warns for out of order unassigned imports (warnOnUnassignedImports enabled) + test(withoutAutofixOutput({ + code: ` + import './local1'; + import global from 'global1'; + import local from './local2'; + import 'global2'; + `, + errors: [ + { + message: '`global1` import should occur before import of `./local1`', + }, + { + message: '`global2` import should occur before import of `./local1`', + }, + ], + options: [{ warnOnUnassignedImports: true }], + })), + // fix cannot move below unassigned import (warnOnUnassignedImports enabled) + test(withoutAutofixOutput({ + code: ` + import local from './local'; + + import 'global1'; + + import global2 from 'global2'; + import global3 from 'global3'; + `, + errors: [{ + message: '`./local` import should occur after import of `global3`', + }], + options: [{ warnOnUnassignedImports: true }], + })), + // Imports inside module declaration + test({ + code: ` + import type { ParsedPath } from 'path'; + import type { CopyOptions } from 'fs'; + + declare module 'my-module' { + import type { ParsedPath } from 'path'; + import type { CopyOptions } from 'fs'; + } + `, + output: ` + import type { CopyOptions } from 'fs'; + import type { ParsedPath } from 'path'; + + declare module 'my-module' { + import type { CopyOptions } from 'fs'; + import type { ParsedPath } from 'path'; + } + `, + errors: [ + { message: '`fs` type import should occur before type import of `path`' }, + { message: '`fs` type import should occur before type import of `path`' }, + ], + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + }, + ], + }), + // named import order + test({ + code: ` + import { type Z, A } from "./Z"; + import type N, { E, D } from "./Z"; + import type { L, G } from "./Z"; + `, + output: ` + import { A, type Z } from "./Z"; + import type N, { D, E } from "./Z"; + import type { G, L } from "./Z"; + `, + ...parserConfig, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [ + { message: `\`A\` import should occur before${supportsImportTypeSpecifiers ? ' type' : ''} import of \`Z\`` }, + { message: '`D` import should occur before import of `E`' }, + { message: '`G` import should occur before import of `L`' }, + ], + }), + test({ + code: ` + const { B, /* Hello World */ A } = require("./Z"); + export { B, A } from "./Z"; + `, + output: ` + const { /* Hello World */ A, B } = require("./Z"); + export { A, B } from "./Z"; + `, + ...parserConfig, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` import should occur before import of `B`', + }, { + message: '`A` export should occur before export of `B`', + }], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups' with all newlines + test({ + code: ` + import c from 'Bar'; + + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + + import type { H } from './bbb'; + `, + output: ` + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + position: 'after' + }, + { + pattern: 'dirB/**', + group: 'internal', + position: 'before' + }, + { + pattern: 'dirC/**', + group: 'internal', + }, + ], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + consolidateIslands: 'inside-groups', + }, + ], + errors: [ + { + message: + 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 2 + }, + { + message: + 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 60 + }, + { + message: + 'There should be no empty lines between this single-line import and the single-line import that follows it', + line: 76 + } + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'inside-groups' with no newlines + test({ + code: ` + import c from 'Bar'; + import d from 'bar'; + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + import type { AA, + BB, CC } from 'abc'; + import type { Z } from 'fizz'; + import type { + A, + B + } from 'foo'; + import type { C2 } from 'dirB/Bar'; + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + output: ` + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + position: 'after' + }, + { + pattern: 'dirB/**', + group: 'internal', + position: 'before' + }, + { + pattern: 'dirC/**', + group: 'internal', + }, + ], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + consolidateIslands: 'inside-groups', + }, + ], + errors: [ + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 3 + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 4, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 13, + }, + { + message: 'There should be at least one empty line between import groups', + line: 22, + }, + { + message: 'There should be at least one empty line between import groups', + line: 23 + }, + { + message: 'There should be at least one empty line between import groups', + line: 24, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 25, + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 27, + }, + { + message: 'There should be at least one empty line between import groups', + line: 28, + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 32, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 33, + }, + { + message: 'There should be at least one empty line between import groups', + line: 38, + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 39, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 40, + }, + { + message: 'There should be at least one empty line between import groups', + line: 46, + }, + { + message: 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 47, + }, + { + message: 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 48, + }, + { + message: 'There should be at least one empty line between import groups', + line: 53, + }, + { + message: 'There should be at least one empty line between import groups', + line: 54, + }, + ], + }), + // Option: sortTypesAmongThemselves: true and newlines-between-types: 'always-and-inside-groups' and consolidateIslands: 'never' (default) + test({ + code: ` + import c from 'Bar'; + import d from 'bar'; + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + import type { AA, + BB, CC } from 'abc'; + import type { Z } from 'fizz'; + import type { + A, + B + } from 'foo'; + import type { C2 } from 'dirB/Bar'; + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + output: ` + import c from 'Bar'; + import d from 'bar'; + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + import type { Z } from 'fizz'; + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + import type { E2 } from 'dirB/baz'; + + import type { C3 } from 'dirC/Bar'; + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + + import type { C1 } from 'dirA/Bar'; + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + import type { E1 } from 'dirA/baz'; + + import type { F } from './index.js'; + + import type { G } from './aaa.js'; + import type { H } from './bbb'; + `, + ...parserConfig, + options: [ + { + alphabetize: { order: 'asc' }, + groups: ['external', 'internal', 'index', 'type'], + pathGroups: [ + { + pattern: 'dirA/**', + group: 'internal', + position: 'after' + }, + { + pattern: 'dirB/**', + group: 'internal', + position: 'before' + }, + { + pattern: 'dirC/**', + group: 'internal', + }, + ], + 'newlines-between': 'always-and-inside-groups', + 'newlines-between-types': 'always-and-inside-groups', + pathGroupsExcludedImportTypes: [], + sortTypesAmongThemselves: true, + consolidateIslands: 'never', + }, + ], + errors: [ + { + message: 'There should be at least one empty line between import groups', + line: 22, + }, + { + message: 'There should be at least one empty line between import groups', + line: 23 + }, + { + message: 'There should be at least one empty line between import groups', + line: 24, + }, + { + message: 'There should be at least one empty line between import groups', + line: 28, + }, + { + message: 'There should be at least one empty line between import groups', + line: 38, + }, + { + message: 'There should be at least one empty line between import groups', + line: 46, + }, + { + message: 'There should be at least one empty line between import groups', + line: 53, + }, + { + message: 'There should be at least one empty line between import groups', + line: 54, + }, + + ], + }), + + // Documentation failing example #1 for newlines-between + test({ + code: ` + import fs from 'fs'; + import path from 'path'; + import sibling from './foo'; + import index from './'; + `, + output: ` + import fs from 'fs'; + import path from 'path'; + + import sibling from './foo'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'always' + } + ], + errors: [ + { + message: 'There should be at least one empty line between import groups', + line: 3, + }, + { + message: 'There should be at least one empty line between import groups', + line: 4, + } + ], + }), + // Documentation failing example #2 for newlines-between + test({ + code: ` + import fs from 'fs'; + + import path from 'path'; + import sibling from './foo'; + import index from './'; + `, + output: ` + import fs from 'fs'; + + import path from 'path'; + + import sibling from './foo'; + + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'always-and-inside-groups' + } + ], + errors: [ + { + message: 'There should be at least one empty line between import groups', + line: 4, + }, + { + message: 'There should be at least one empty line between import groups', + line: 5, + } + ], + }), + // Documentation failing example #3 for newlines-between + test({ + code: ` + import fs from 'fs'; + import path from 'path'; + + import sibling from './foo'; + + import index from './'; + `, + output: ` + import fs from 'fs'; + import path from 'path'; + import sibling from './foo'; + import index from './'; + `, + ...parserConfig, + options: [ + { + 'newlines-between': 'never' + } + ], + errors: [ + { + message: 'There should be no empty line between import groups', + line: 3, + }, + { + message: 'There should be no empty line between import groups', + line: 5, + } ], }), - // warns for out of order unassigned imports (warnOnUnassignedImports enabled) - test(withoutAutofixOutput({ + // Documentation failing example #1 for alphabetize + test({ code: ` - import './local1'; - import global from 'global1'; - import local from './local2'; - import 'global2'; + import React, { PureComponent } from 'react'; + import aTypes from 'prop-types'; + import { compose, apply } from 'xcompose'; + import * as classnames from 'classnames'; + import blist2 from 'blist'; + import blist from 'BList'; + `, + // The reason why this output does not match the success example after being fixed is because eslint will leave overlapping errors alone, so only one import gets reordered when fixes are applied + output: ` + import aTypes from 'prop-types'; + import React, { PureComponent } from 'react'; + import { compose, apply } from 'xcompose'; + import * as classnames from 'classnames'; + import blist2 from 'blist'; + import blist from 'BList'; `, + ...parserConfig, + options: [ + { + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], errors: [ { - message: '`global1` import should occur before import of `./local1`', + message: '`prop-types` import should occur before import of `react`', + line: 3, }, { - message: '`global2` import should occur before import of `./local1`', + message: '`classnames` import should occur before import of `react`', + line: 5, + }, + { + message: '`blist` import should occur before import of `react`', + line: 6, }, + { + message: '`BList` import should occur before import of `react`', + line: 7, + } ], - options: [{ warnOnUnassignedImports: true }], - })), - // fix cannot move below unassigned import (warnOnUnassignedImports enabled) - test(withoutAutofixOutput({ + }), + // Documentation failing example #1 for named + test({ code: ` - import local from './local'; - - import 'global1'; - - import global2 from 'global2'; - import global3 from 'global3'; + import { compose, apply } from 'xcompose'; `, - errors: [{ - message: '`./local` import should occur after import of `global3`', - }], - options: [{ warnOnUnassignedImports: true }], - })), - // Imports inside module declaration + output: ` + import { apply, compose } from 'xcompose'; + `, + ...parserConfig, + options: [ + { + "named": true, + "alphabetize": { + "order": "asc" + } + } + ], + errors: [ + { + message: '`apply` import should occur before import of `compose`', + line: 2, + } + ], + }), + // Documentation failing example #1 for warnOnUnassignedImports test({ code: ` - import type { ParsedPath } from 'path'; - import type { CopyOptions } from 'fs'; - - declare module 'my-module' { - import type { ParsedPath } from 'path'; - import type { CopyOptions } from 'fs'; + import fs from 'fs'; + import './styles.css'; + import path from 'path'; + `, + // Should not be fixed + output: ` + import fs from 'fs'; + import './styles.css'; + import path from 'path'; + `, + ...parserConfig, + options: [ + { + "warnOnUnassignedImports": true + } + ], + errors: [ + { + message: '`path` import should occur before import of `./styles.css`', + line: 4, } + ], + }), + // Documentation failing example #1 for sortTypesAmongThemselves + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; `, + // This is the "correct" behavior, but it's the wrong outcome (expectedly) output: ` - import type { CopyOptions } from 'fs'; - import type { ParsedPath } from 'path'; - - declare module 'my-module' { - import type { CopyOptions } from 'fs'; - import type { ParsedPath } from 'path'; + import type C from "../foo.js"; + import type A from "fs"; + import type B from "path"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + import c from "../foo.js"; + import d from "./bar.js"; + import e from "./"; + `, + ...parserConfig, + options: [ + { + "groups": ["type", "builtin", "parent", "sibling", "index"], + "alphabetize": { "order": "asc" } } + ], + errors: [ + { + message: '`../foo.js` type import should occur before type import of `fs`', + line: 4, + }, + { + message: '`./bar.js` type import should occur before type import of `fs`', + line: 5, + }, + { + message: '`./` type import should occur before type import of `fs`', + line: 6, + }, + ], + }), + // Documentation failing example #1 for newlines-between-types + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; + `, + output: ` + import type A from "fs"; + import type B from "path"; + + import type C from "../foo.js"; + + import type D from "./bar.js"; + + import type E from './'; + + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; `, + ...parserConfig, + options: [ + { + groups: ['type', 'builtin', 'parent', 'sibling', 'index'], + sortTypesAmongThemselves: true, + 'newlines-between': 'always' + } + ], errors: [ - { message: '`fs` type import should occur before type import of `path`' }, - { message: '`fs` type import should occur before type import of `path`' }, + { + message: 'There should be at least one empty line between import groups', + line: 3, + }, + { + message: 'There should be at least one empty line between import groups', + line: 4, + }, + { + message: 'There should be at least one empty line between import groups', + line: 5, + }, ], + }), + // Documentation failing example #2 for newlines-between-types + test({ + code: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; + `, + output: ` + import type A from "fs"; + import type B from "path"; + import type C from "../foo.js"; + import type D from "./bar.js"; + import type E from './'; + import a from "fs"; + import b from "path"; + + import c from "../foo.js"; + + import d from "./bar.js"; + + import e from "./"; + `, ...parserConfig, options: [ { - alphabetize: { order: 'asc' }, + groups: ['type', 'builtin', 'parent', 'sibling', 'index'], + sortTypesAmongThemselves: true, + 'newlines-between': 'always', + 'newlines-between-types': 'never' + } + ], + errors: [ + { + message: 'There should be no empty line between import groups', + line: 6, }, ], }), - // named import order + // Documentation failing example #1 for consolidateIslands test({ code: ` - import { type Z, A } from "./Z"; - import type N, { E, D } from "./Z"; - import type { L, G } from "./Z"; + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + var async = require('async'); + var relParent1 = require('../foo'); + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + var relParent3 = require('../bar'); + var { sibling1, + sibling2, sibling3 } = require('./foo'); + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); `, output: ` - import { A, type Z } from "./Z"; - import type N, { D, E } from "./Z"; - import type { G, L } from "./Z"; + var fs = require('fs'); + var path = require('path'); + var { util1, util2, util3 } = require('util'); + + var async = require('async'); + + var relParent1 = require('../foo'); + + var { + relParent21, + relParent22, + relParent23, + relParent24, + } = require('../'); + + var relParent3 = require('../bar'); + + var { sibling1, + sibling2, sibling3 } = require('./foo'); + + var sibling2 = require('./bar'); + var sibling3 = require('./foobar'); `, ...parserConfig, - options: [{ - named: true, - alphabetize: { order: 'asc' }, - }], + options: [ + { + "newlines-between": "always-and-inside-groups", + "consolidateIslands": "inside-groups" + } + ], errors: [ - { message: `\`A\` import should occur before${supportsImportTypeSpecifiers ? ' type' : ''} import of \`Z\`` }, - { message: '`D` import should occur before import of `E`' }, - { message: '`G` import should occur before import of `L`' }, + { + message: 'There should be at least one empty line between import groups', + line: 4 + }, + { + message: 'There should be at least one empty line between import groups', + line: 5 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 6 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 12 + }, + { + message: 'There should be at least one empty line between import groups', + line: 13 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 15 + } ], }), + // Documentation failing example #2 for consolidateIslands test({ code: ` - const { B, /* Hello World */ A } = require("./Z"); - export { B, A } from "./Z"; + import c from 'Bar'; + import d from 'bar'; + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + import a from 'foo'; + import b from 'dirA/bar'; + import index from './'; + import type { AA, + BB, CC } from 'abc'; + import type { Z } from 'fizz'; + import type { + A, + B + } from 'foo'; + import type { C2 } from 'dirB/Bar'; + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; `, output: ` - const { /* Hello World */ A, B } = require("./Z"); - export { A, B } from "./Z"; + import c from 'Bar'; + import d from 'bar'; + + import { + aa, + bb, + cc, + dd, + ee, + ff, + gg + } from 'baz'; + + import { + hh, + ii, + jj, + kk, + ll, + mm, + nn + } from 'fizz'; + + import a from 'foo'; + + import b from 'dirA/bar'; + + import index from './'; + + import type { AA, + BB, CC } from 'abc'; + + import type { Z } from 'fizz'; + + import type { + A, + B + } from 'foo'; + + import type { C2 } from 'dirB/Bar'; + + import type { + D2, + X2, + Y2 + } from 'dirB/bar'; + + import type { E2 } from 'dirB/baz'; + import type { C3 } from 'dirC/Bar'; + + import type { + D3, + X3, + Y3 + } from 'dirC/bar'; + + import type { E3 } from 'dirC/baz'; + import type { F3 } from 'dirC/caz'; + import type { C1 } from 'dirA/Bar'; + + import type { + D1, + X1, + Y1 + } from 'dirA/bar'; + + import type { E1 } from 'dirA/baz'; + import type { F } from './index.js'; + import type { G } from './aaa.js'; + import type { H } from './bbb'; `, ...parserConfig, - options: [{ - named: true, - alphabetize: { order: 'asc' }, - }], - errors: [{ - message: '`A` import should occur before import of `B`', - }, { - message: '`A` export should occur before export of `B`', - }], + options: [ + { + "alphabetize": { "order": "asc" }, + "groups": ["external", "internal", "index", "type"], + "pathGroups": [ + { + "pattern": "dirA/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "dirB/**", + "group": "internal", + "position": "before" + }, + { + "pattern": "dirC/**", + "group": "internal" + } + ], + "newlines-between": "always-and-inside-groups", + "newlines-between-types": "never", + "pathGroupsExcludedImportTypes": [], + "sortTypesAmongThemselves": true, + "consolidateIslands": "inside-groups" + } + ], + errors: [ + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 3 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 4 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 13 + }, + { + message: 'There should be at least one empty line between import groups', + line: 22 + }, + { + message: 'There should be at least one empty line between import groups', + line: 23 + }, + { + message: 'There should be at least one empty line between import groups', + line: 24 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 25 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 27 + }, + { + message: 'There should be at least one empty line between import groups', + line: 28 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 32 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 33 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 39 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 40 + }, + { + message: + 'There should be at least one empty line between this import and the multi-line import that follows it', + line: 47 + }, + { + message: + 'There should be at least one empty line between this multi-line import and the import that follows it', + line: 48 + } + ], }), supportsExportTypeSpecifiers ? [