Skip to content

Commit

Permalink
feat: support require by sort-imports rule
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Jul 20, 2023
1 parent 3d65cca commit 2f8b805
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 15 deletions.
67 changes: 52 additions & 15 deletions rules/sort-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const RULE_NAME = 'sort-imports'

type ModuleDeclaration =
| TSESTree.TSImportEqualsDeclaration
| TSESTree.VariableDeclaration
| TSESTree.ImportDeclaration

type SortingNodeWithGroup<T extends string[]> = SortingNode & {
Expand Down Expand Up @@ -165,6 +166,13 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({

let nodes: SortingNodeWithGroup<string[]>[] = []

let getRequireModuleName = (node: TSESTree.VariableDeclaration) =>
(
(
node.declarations.at(0)!.init! as TSESTree.CallExpression
).arguments.at(0) as TSESTree.Literal
).value as string

let computeGroup = (node: ModuleDeclaration): Group<string[]> => {
let group: Group<string[]> | undefined

Expand Down Expand Up @@ -194,12 +202,12 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
}
}

let isInternal = (nodeElement: TSESTree.ImportDeclaration) =>
let isInternal = (value: string) =>
(options['internal-pattern'].length &&
options['internal-pattern'].some(pattern =>
minimatch(nodeElement.source.value, pattern),
minimatch(value, pattern),
)) ||
tsPaths.some(pattern => minimatch(nodeElement.source.value, pattern))
tsPaths.some(pattern => minimatch(value, pattern))

let determineCustomGroup = (
groupType: 'value' | 'type',
Expand All @@ -221,15 +229,15 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
}
}

if (node.importKind === 'type') {
if (node.type !== 'VariableDeclaration' && node.importKind === 'type') {
if (node.type === AST_NODE_TYPES.ImportDeclaration) {
determineCustomGroup('type', node.source.value)

if (isCoreModule(node.source.value)) {
defineGroup('builtin-type')
}

if (isInternal(node)) {
if (isInternal(node.source.value)) {
defineGroup('internal-type')
}

Expand All @@ -250,34 +258,49 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
defineGroup('type')
}

if (!group && node.type === AST_NODE_TYPES.ImportDeclaration) {
determineCustomGroup('value', node.source.value)
if (
!group &&
(node.type === AST_NODE_TYPES.ImportDeclaration ||
node.type === AST_NODE_TYPES.VariableDeclaration)
) {
let value

if (isCoreModule(node.source.value)) {
if (node.type === AST_NODE_TYPES.ImportDeclaration) {
;({ value } = node.source)
} else {
value = getRequireModuleName(node)
}

determineCustomGroup('value', value)

if (isCoreModule(value)) {
defineGroup('builtin')
}

if (isInternal(node)) {
if (isInternal(value)) {
defineGroup('internal')
}

if (isStyle(node.source.value)) {
if (isStyle(value)) {
defineGroup('style')
}

if (node.specifiers.length === 0) {
if (
node.type === AST_NODE_TYPES.ImportDeclaration &&
node.specifiers.length === 0
) {
defineGroup('side-effect')
}

if (isIndex(node.source.value)) {
if (isIndex(value)) {
defineGroup('index')
}

if (isParent(node.source.value)) {
if (isParent(value)) {
defineGroup('parent')
}

if (isSibling(node.source.value)) {
if (isSibling(value)) {
defineGroup('sibling')
}

Expand All @@ -292,7 +315,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({

if (node.type === AST_NODE_TYPES.ImportDeclaration) {
name = node.source.value
} else {
} else if (node.type === AST_NODE_TYPES.TSImportEqualsDeclaration) {
if (
node.moduleReference.type ===
AST_NODE_TYPES.TSExternalModuleReference &&
Expand All @@ -302,6 +325,8 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
} else {
name = source.text.slice(...node.moduleReference.range)
}
} else {
name = getRequireModuleName(node)
}

nodes.push({
Expand All @@ -315,6 +340,18 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
return {
TSImportEqualsDeclaration: registerNode,
ImportDeclaration: registerNode,
CallExpression: node => {
if (
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments.length === 1 &&
node.arguments.at(0)!.type === 'Literal' &&
node.parent?.type === 'VariableDeclarator' &&
node.parent.parent?.type === 'VariableDeclaration'
) {
registerNode(node.parent.parent)
}
},
'Program:exit': () => {
let getGroupNumber = (node: SortingNodeWithGroup<string[]>): number => {
for (let i = 0, max = options.groups.length; i < max; i++) {
Expand Down
215 changes: 215 additions & 0 deletions test/sort-imports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,80 @@ describe(RULE_NAME, () => {
],
})
})

it(`${RULE_NAME}(${type}): supports require imports`, () => {
ruleTester.run(RULE_NAME, rule, {
valid: [],
invalid: [
{
code: dedent`
let { GantaNakami, IsakiMagari } = require('insomniacs')
let TaoUkegawa = require('./school/ukegawa')
let MotokoKanikawa = require('./school/kanikawa')
let KanamiAnamizu = require('./school/anamizu')
let { YuiShiromaru } = require('astronomy-club')
`,
output: dedent`
let { YuiShiromaru } = require('astronomy-club')
let { GantaNakami, IsakiMagari } = require('insomniacs')
let KanamiAnamizu = require('./school/anamizu')
let MotokoKanikawa = require('./school/kanikawa')
let TaoUkegawa = require('./school/ukegawa')
`,
options: [
{
type: SortType.alphabetical,
order: SortOrder.asc,
'ignore-case': true,
'newlines-between': NewlinesBetweenValue.always,
'internal-pattern': ['~/**'],
groups: [
'type',
['builtin', 'external'],
'internal-type',
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
],
},
],
errors: [
{
messageId: 'missedSpacingBetweenImports',
data: {
left: 'insomniacs',
right: './school/ukegawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/ukegawa',
right: './school/kanikawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/kanikawa',
right: './school/anamizu',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/anamizu',
right: 'astronomy-club',
},
},
],
},
],
})
})
})

describe(`${RULE_NAME}: sorting by natural order`, () => {
Expand Down Expand Up @@ -2106,6 +2180,80 @@ describe(RULE_NAME, () => {
],
})
})

it(`${RULE_NAME}(${type}): supports require imports`, () => {
ruleTester.run(RULE_NAME, rule, {
valid: [],
invalid: [
{
code: dedent`
let { GantaNakami, IsakiMagari } = require('insomniacs')
let TaoUkegawa = require('./school/ukegawa')
let MotokoKanikawa = require('./school/kanikawa')
let KanamiAnamizu = require('./school/anamizu')
let { YuiShiromaru } = require('astronomy-club')
`,
output: dedent`
let { YuiShiromaru } = require('astronomy-club')
let { GantaNakami, IsakiMagari } = require('insomniacs')
let KanamiAnamizu = require('./school/anamizu')
let MotokoKanikawa = require('./school/kanikawa')
let TaoUkegawa = require('./school/ukegawa')
`,
options: [
{
type: SortType.natural,
order: SortOrder.asc,
'ignore-case': true,
'newlines-between': NewlinesBetweenValue.always,
'internal-pattern': ['~/**'],
groups: [
'type',
['builtin', 'external'],
'internal-type',
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
],
},
],
errors: [
{
messageId: 'missedSpacingBetweenImports',
data: {
left: 'insomniacs',
right: './school/ukegawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/ukegawa',
right: './school/kanikawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/kanikawa',
right: './school/anamizu',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/anamizu',
right: 'astronomy-club',
},
},
],
},
],
})
})
})

describe(`${RULE_NAME}: sorting by line length`, () => {
Expand Down Expand Up @@ -3204,6 +3352,73 @@ describe(RULE_NAME, () => {
],
})
})

it(`${RULE_NAME}(${type}): supports require imports`, () => {
ruleTester.run(RULE_NAME, rule, {
valid: [],
invalid: [
{
code: dedent`
let { GantaNakami, IsakiMagari } = require('insomniacs')
let TaoUkegawa = require('./school/ukegawa')
let MotokoKanikawa = require('./school/kanikawa')
let KanamiAnamizu = require('./school/anamizu')
let { YuiShiromaru } = require('astronomy-club')
`,
output: dedent`
let { GantaNakami, IsakiMagari } = require('insomniacs')
let { YuiShiromaru } = require('astronomy-club')
let MotokoKanikawa = require('./school/kanikawa')
let KanamiAnamizu = require('./school/anamizu')
let TaoUkegawa = require('./school/ukegawa')
`,
options: [
{
type: SortType['line-length'],
order: SortOrder.desc,
'ignore-case': true,
'newlines-between': NewlinesBetweenValue.always,
'internal-pattern': ['~/**'],
groups: [
'type',
['builtin', 'external'],
'internal-type',
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
],
},
],
errors: [
{
messageId: 'missedSpacingBetweenImports',
data: {
left: 'insomniacs',
right: './school/ukegawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/ukegawa',
right: './school/kanikawa',
},
},
{
messageId: 'unexpectedImportsOrder',
data: {
left: './school/anamizu',
right: 'astronomy-club',
},
},
],
},
],
})
})
})

describe(`${RULE_NAME}: misc`, () => {
Expand Down

0 comments on commit 2f8b805

Please sign in to comment.