@borela-tech/eslint-config 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +82 -0
- package/README.md +67 -0
- package/dist/index.js +466 -38
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
- package/src/index.ts +9 -0
- package/src/lib/compare.ts +3 -0
- package/src/rules/__tests__/importsAndReExportsAtTop.test.ts +118 -0
- package/src/rules/__tests__/individualReExports.test.ts +62 -0
- package/src/rules/__tests__/sortedImports.test.ts +3 -3
- package/src/rules/__tests__/sortedReExports.test.ts +151 -0
- package/src/rules/importsAndReExportsAtTop/CategorizedStatements.ts +8 -0
- package/src/rules/importsAndReExportsAtTop/ReExport.ts +5 -0
- package/src/rules/importsAndReExportsAtTop/StatementIndices.ts +5 -0
- package/src/rules/importsAndReExportsAtTop/categorizeStatements.ts +27 -0
- package/src/rules/importsAndReExportsAtTop/findFirstIndices.ts +25 -0
- package/src/rules/importsAndReExportsAtTop/generateSortedText.ts +18 -0
- package/src/rules/importsAndReExportsAtTop/getStatementType.ts +17 -0
- package/src/rules/importsAndReExportsAtTop/hasViolation.ts +29 -0
- package/src/rules/importsAndReExportsAtTop/index.ts +45 -0
- package/src/rules/importsAndReExportsAtTop/statementType.ts +4 -0
- package/src/rules/individualImports.ts +5 -14
- package/src/rules/individualReExports.ts +52 -0
- package/src/rules/sortedImports/CategorizedImport.ts +2 -2
- package/src/rules/sortedImports/ImportError.ts +2 -2
- package/src/rules/sortedImports/ImportGroup.ts +5 -1
- package/src/rules/sortedImports/ImportGroupOrder.ts +8 -0
- package/src/rules/sortedImports/areSpecifiersSorted.ts +4 -5
- package/src/rules/sortedImports/categorizeImport.ts +4 -0
- package/src/rules/sortedImports/checkAlphabeticalSorting.ts +4 -3
- package/src/rules/sortedImports/checkGroupOrdering.ts +2 -3
- package/src/rules/sortedImports/createFix/buildSortedCode.ts +2 -2
- package/src/rules/sortedImports/createFix/formatNamedImport.ts +5 -8
- package/src/rules/sortedImports/createFix/getReplacementRange.ts +1 -1
- package/src/rules/sortedImports/createFix/sortImportGroups.ts +5 -4
- package/src/rules/sortedImports/getNamedSpecifiers.ts +3 -4
- package/src/rules/sortedImports/getSortKey.ts +3 -3
- package/src/rules/sortedImports/getSpecifierName.ts +2 -2
- package/src/rules/sortedImports/sortSpecifiersText.ts +7 -6
- package/src/rules/sortedReExports/CategorizedNamedReExport.ts +6 -0
- package/src/rules/sortedReExports/CategorizedReExport.ts +15 -0
- package/src/rules/sortedReExports/ReExportDeclaration.ts +5 -0
- package/src/rules/sortedReExports/ReExportError.ts +6 -0
- package/src/rules/sortedReExports/ReExportGroup.ts +4 -0
- package/src/rules/sortedReExports/ReExportGroupOrder.ts +7 -0
- package/src/rules/sortedReExports/areSpecifiersSorted.ts +9 -0
- package/src/rules/sortedReExports/categorizeReExport.ts +17 -0
- package/src/rules/sortedReExports/categorizeReExports.ts +14 -0
- package/src/rules/sortedReExports/checkAlphabeticalSorting.ts +25 -0
- package/src/rules/sortedReExports/checkGroupOrdering.ts +21 -0
- package/src/rules/sortedReExports/checkSpecifiersSorting.ts +23 -0
- package/src/rules/sortedReExports/createFix/buildSortedCode.ts +28 -0
- package/src/rules/sortedReExports/createFix/findFirstExportIndex.ts +11 -0
- package/src/rules/sortedReExports/createFix/findLastExportIndex.ts +12 -0
- package/src/rules/sortedReExports/createFix/formatNamedReExport.ts +20 -0
- package/src/rules/sortedReExports/createFix/getReplacementRange.ts +22 -0
- package/src/rules/sortedReExports/createFix/groupReExportsByType.ts +17 -0
- package/src/rules/sortedReExports/createFix/index.ts +29 -0
- package/src/rules/sortedReExports/createFix/sortExportGroups.ts +11 -0
- package/src/rules/sortedReExports/getNamedSpecifiers.ts +9 -0
- package/src/rules/sortedReExports/getReExportDeclarations.ts +12 -0
- package/src/rules/sortedReExports/getSortKey.ts +16 -0
- package/src/rules/sortedReExports/getSpecifierName.ts +7 -0
- package/src/rules/sortedReExports/index.ts +54 -0
- package/src/rules/sortedReExports/isNamedReExport.ts +6 -0
- package/src/rules/sortedReExports/sortSpecifiersText.ts +15 -0
- /package/src/{rules/sortedImports/createFix → lib}/ReplacementRange.ts +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {reExportGroupOrder} from './ReExportGroupOrder'
|
|
2
|
+
import type {CategorizedReExport} from './CategorizedReExport'
|
|
3
|
+
import type {ReExportError} from './ReExportError'
|
|
4
|
+
|
|
5
|
+
export function checkGroupOrdering(categorized: CategorizedReExport[]): ReExportError[] {
|
|
6
|
+
const errors: ReExportError[] = []
|
|
7
|
+
|
|
8
|
+
let currentGroupIndex = -1
|
|
9
|
+
for (const {declaration, group} of categorized) {
|
|
10
|
+
const groupIndex = reExportGroupOrder.indexOf(group)
|
|
11
|
+
if (groupIndex < currentGroupIndex) {
|
|
12
|
+
errors.push({
|
|
13
|
+
node: declaration,
|
|
14
|
+
messageId: 'wrongGroup',
|
|
15
|
+
})
|
|
16
|
+
} else
|
|
17
|
+
currentGroupIndex = groupIndex
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return errors
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {areSpecifiersSorted} from './areSpecifiersSorted'
|
|
2
|
+
import {getNamedSpecifiers} from './getNamedSpecifiers'
|
|
3
|
+
import {isNamedReExport} from './isNamedReExport'
|
|
4
|
+
import type {CategorizedReExport} from './CategorizedReExport'
|
|
5
|
+
import type {ReExportError} from './ReExportError'
|
|
6
|
+
|
|
7
|
+
export function checkSpecifiersSorting(categorized: CategorizedReExport[]): ReExportError[] {
|
|
8
|
+
const errors: ReExportError[] = []
|
|
9
|
+
const namedReExports = categorized.filter(isNamedReExport)
|
|
10
|
+
|
|
11
|
+
for (const {declaration} of namedReExports) {
|
|
12
|
+
const specifiers = getNamedSpecifiers(declaration)
|
|
13
|
+
const isSorted = areSpecifiersSorted(specifiers)
|
|
14
|
+
if (specifiers.length > 1 && !isSorted) {
|
|
15
|
+
errors.push({
|
|
16
|
+
node: declaration,
|
|
17
|
+
messageId: 'sortedNames',
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return errors
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {formatNamedReExport} from './formatNamedReExport'
|
|
2
|
+
import {isNamedReExport} from '../isNamedReExport'
|
|
3
|
+
import {reExportGroupOrder} from '../ReExportGroupOrder'
|
|
4
|
+
import type {CategorizedReExport} from '../CategorizedReExport'
|
|
5
|
+
import type {ReExportGroup} from '../ReExportGroup'
|
|
6
|
+
|
|
7
|
+
export function buildSortedCode(
|
|
8
|
+
grouped: Record<ReExportGroup, CategorizedReExport[]>,
|
|
9
|
+
sourceCode: {getText: (node?: unknown) => string},
|
|
10
|
+
): string[] {
|
|
11
|
+
const sortedCode: string[] = []
|
|
12
|
+
|
|
13
|
+
for (const group of reExportGroupOrder) {
|
|
14
|
+
for (const item of grouped[group]) {
|
|
15
|
+
if (isNamedReExport(item)) {
|
|
16
|
+
sortedCode.push(
|
|
17
|
+
formatNamedReExport(
|
|
18
|
+
item.declaration,
|
|
19
|
+
sourceCode,
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
} else
|
|
23
|
+
sortedCode.push(sourceCode.getText(item.declaration))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return sortedCode
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
2
|
+
|
|
3
|
+
export function findFirstExportIndex(programBody: TSESTree.ProgramStatement[]): number {
|
|
4
|
+
for (let i = 0; i < programBody.length; i++) {
|
|
5
|
+
if (programBody[i].type === 'ExportNamedDeclaration'
|
|
6
|
+
|| programBody[i].type === 'ExportAllDeclaration') {
|
|
7
|
+
return i
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return -1
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
2
|
+
|
|
3
|
+
export function findLastExportIndex(programBody: TSESTree.ProgramStatement[]): number {
|
|
4
|
+
let lastIndex = -1
|
|
5
|
+
for (let i = 0; i < programBody.length; i++) {
|
|
6
|
+
if (programBody[i].type === 'ExportNamedDeclaration'
|
|
7
|
+
|| programBody[i].type === 'ExportAllDeclaration') {
|
|
8
|
+
lastIndex = i
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return lastIndex
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {areSpecifiersSorted} from '../areSpecifiersSorted'
|
|
2
|
+
import {getNamedSpecifiers} from '../getNamedSpecifiers'
|
|
3
|
+
import {sortSpecifiersText} from '../sortSpecifiersText'
|
|
4
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
5
|
+
|
|
6
|
+
export function formatNamedReExport(
|
|
7
|
+
declaration: TSESTree.ExportNamedDeclaration,
|
|
8
|
+
sourceCode: {getText: (node?: unknown) => string},
|
|
9
|
+
): string {
|
|
10
|
+
const specifiers = getNamedSpecifiers(declaration)
|
|
11
|
+
|
|
12
|
+
if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {
|
|
13
|
+
const sortedSpecifiers = sortSpecifiersText(specifiers, sourceCode)
|
|
14
|
+
const source = declaration.source!.value
|
|
15
|
+
const prefix = declaration.exportKind === 'type' ? 'export type ' : 'export '
|
|
16
|
+
return `${prefix}{${sortedSpecifiers}} from '${source}'`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return sourceCode.getText(declaration)
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {findFirstExportIndex} from './findFirstExportIndex'
|
|
2
|
+
import {findLastExportIndex} from './findLastExportIndex'
|
|
3
|
+
import type {ReplacementRange} from '../../../lib/ReplacementRange'
|
|
4
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
5
|
+
|
|
6
|
+
export function getReplacementRange(
|
|
7
|
+
programBody: TSESTree.ProgramStatement[],
|
|
8
|
+
): ReplacementRange {
|
|
9
|
+
const firstIndex = findFirstExportIndex(programBody)
|
|
10
|
+
const lastIndex = findLastExportIndex(programBody)
|
|
11
|
+
|
|
12
|
+
if (firstIndex === -1 || lastIndex === -1)
|
|
13
|
+
return {start: 0, end: 0}
|
|
14
|
+
|
|
15
|
+
const firstExport = programBody[firstIndex]
|
|
16
|
+
const lastExport = programBody[lastIndex]
|
|
17
|
+
|
|
18
|
+
const start = firstExport.range[0]
|
|
19
|
+
const end = lastExport.range[1]
|
|
20
|
+
|
|
21
|
+
return {start, end}
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {CategorizedReExport} from '../CategorizedReExport'
|
|
2
|
+
import type {ReExportGroup} from '../ReExportGroup'
|
|
3
|
+
|
|
4
|
+
export function groupReExportsByType(
|
|
5
|
+
categorized: CategorizedReExport[],
|
|
6
|
+
): Record<ReExportGroup, CategorizedReExport[]> {
|
|
7
|
+
const grouped: Record<ReExportGroup, CategorizedReExport[]> = {
|
|
8
|
+
're-export-all': [],
|
|
9
|
+
're-export-named': [],
|
|
10
|
+
're-export-type': [],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const item of categorized)
|
|
14
|
+
grouped[item.group].push(item)
|
|
15
|
+
|
|
16
|
+
return grouped
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {buildSortedCode} from './buildSortedCode'
|
|
2
|
+
import {categorizeReExports} from '../categorizeReExports'
|
|
3
|
+
import {getReplacementRange} from './getReplacementRange'
|
|
4
|
+
import {groupReExportsByType} from './groupReExportsByType'
|
|
5
|
+
import {ReExportDeclaration} from '../ReExportDeclaration'
|
|
6
|
+
import {sortExportGroups} from './sortExportGroups'
|
|
7
|
+
import type {Rule} from 'eslint'
|
|
8
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
9
|
+
|
|
10
|
+
export function createFix(
|
|
11
|
+
fixer: Rule.RuleFixer,
|
|
12
|
+
reExportDeclarations: ReExportDeclaration[],
|
|
13
|
+
sourceCode: {getText: (node?: unknown) => string},
|
|
14
|
+
programBody: TSESTree.ProgramStatement[],
|
|
15
|
+
) {
|
|
16
|
+
const range = getReplacementRange(programBody)
|
|
17
|
+
const categorized = categorizeReExports(reExportDeclarations)
|
|
18
|
+
const grouped = groupReExportsByType(categorized)
|
|
19
|
+
|
|
20
|
+
sortExportGroups(grouped)
|
|
21
|
+
|
|
22
|
+
const sortedCode = buildSortedCode(grouped, sourceCode)
|
|
23
|
+
.join('\n')
|
|
24
|
+
|
|
25
|
+
return fixer.replaceTextRange(
|
|
26
|
+
[range.start, range.end],
|
|
27
|
+
sortedCode,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {compare} from '../../../lib/compare'
|
|
2
|
+
import type {CategorizedReExport} from '../CategorizedReExport'
|
|
3
|
+
import type {ReExportGroup} from '../ReExportGroup'
|
|
4
|
+
|
|
5
|
+
export function sortExportGroups(
|
|
6
|
+
grouped: Record<ReExportGroup, CategorizedReExport[]>,
|
|
7
|
+
): void {
|
|
8
|
+
grouped['re-export-all'].sort((a, b) => compare(a.sortKey, b.sortKey))
|
|
9
|
+
grouped['re-export-named'].sort((a, b) => compare(a.sortKey, b.sortKey))
|
|
10
|
+
grouped['re-export-type'].sort((a, b) => compare(a.sortKey, b.sortKey))
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
2
|
+
|
|
3
|
+
export function getNamedSpecifiers(
|
|
4
|
+
declaration: TSESTree.ExportNamedDeclaration,
|
|
5
|
+
): TSESTree.ExportSpecifier[] {
|
|
6
|
+
return declaration.specifiers.filter(
|
|
7
|
+
s => s.type === 'ExportSpecifier' && s.local.type === 'Identifier',
|
|
8
|
+
) as TSESTree.ExportSpecifier[]
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {ReExportDeclaration} from './ReExportDeclaration'
|
|
2
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
3
|
+
|
|
4
|
+
export function getReExportDeclarations(
|
|
5
|
+
programBody: TSESTree.ProgramStatement[],
|
|
6
|
+
): ReExportDeclaration[] {
|
|
7
|
+
return programBody.filter(
|
|
8
|
+
(statement): statement is ReExportDeclaration =>
|
|
9
|
+
(statement.type === 'ExportNamedDeclaration' && statement.source !== null)
|
|
10
|
+
|| statement.type === 'ExportAllDeclaration',
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
2
|
+
|
|
3
|
+
export function getSortKey(
|
|
4
|
+
declaration: TSESTree.ExportNamedDeclaration | TSESTree.ExportAllDeclaration,
|
|
5
|
+
): string {
|
|
6
|
+
if (declaration.type === 'ExportAllDeclaration')
|
|
7
|
+
return declaration.source.value
|
|
8
|
+
|
|
9
|
+
const specifier = declaration.specifiers[0]
|
|
10
|
+
if (!specifier)
|
|
11
|
+
return ''
|
|
12
|
+
|
|
13
|
+
return specifier.local.type === 'Identifier'
|
|
14
|
+
? specifier.local.name
|
|
15
|
+
: specifier.local.value
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {categorizeReExports} from './categorizeReExports'
|
|
2
|
+
import {checkAlphabeticalSorting} from './checkAlphabeticalSorting'
|
|
3
|
+
import {checkGroupOrdering} from './checkGroupOrdering'
|
|
4
|
+
import {checkSpecifiersSorting} from './checkSpecifiersSorting'
|
|
5
|
+
import {createFix} from './createFix'
|
|
6
|
+
import {getReExportDeclarations} from './getReExportDeclarations'
|
|
7
|
+
import type {ReExportError} from './ReExportError'
|
|
8
|
+
import type {Rule} from 'eslint'
|
|
9
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
10
|
+
|
|
11
|
+
export const sortedReExports: Rule.RuleModule = {
|
|
12
|
+
meta: {
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Enforce sorted exports alphabetically',
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
messages: {
|
|
19
|
+
sortedReExports: 'Exports should be sorted alphabetically',
|
|
20
|
+
sortedNames: 'Named exports should be sorted alphabetically',
|
|
21
|
+
wrongGroup: 'Export is in wrong group',
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
type: 'suggestion',
|
|
25
|
+
},
|
|
26
|
+
create(context) {
|
|
27
|
+
return {
|
|
28
|
+
Program(node) {
|
|
29
|
+
const body = node.body as TSESTree.ProgramStatement[]
|
|
30
|
+
const declarations = getReExportDeclarations(body)
|
|
31
|
+
if (declarations.length === 0)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
const categorized = categorizeReExports(declarations)
|
|
35
|
+
const errors: ReExportError[] = [
|
|
36
|
+
...checkGroupOrdering(categorized),
|
|
37
|
+
...checkAlphabeticalSorting(categorized),
|
|
38
|
+
...checkSpecifiersSorting(categorized),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
for (const error of errors) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: error.node,
|
|
44
|
+
messageId: error.messageId,
|
|
45
|
+
fix(fixer) {
|
|
46
|
+
const sourceCode = context.sourceCode
|
|
47
|
+
return createFix(fixer, declarations, sourceCode, body)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {compare} from '../../lib/compare'
|
|
2
|
+
import {getSpecifierName} from './getSpecifierName'
|
|
3
|
+
import type {TSESTree} from '@typescript-eslint/types'
|
|
4
|
+
|
|
5
|
+
export function sortSpecifiersText(
|
|
6
|
+
specifiers: TSESTree.ExportSpecifier[],
|
|
7
|
+
sourceCode: {getText: (node?: unknown) => string},
|
|
8
|
+
): string {
|
|
9
|
+
const sorted = [...specifiers].sort((a, b) => {
|
|
10
|
+
const nameA = getSpecifierName(a)
|
|
11
|
+
const nameB = getSpecifierName(b)
|
|
12
|
+
return compare(nameA, nameB)
|
|
13
|
+
})
|
|
14
|
+
return sorted.map(s => sourceCode.getText(s)).join(', ')
|
|
15
|
+
}
|
|
File without changes
|