@borela-tech/eslint-config 2.1.2 → 2.2.1

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.
Files changed (78) hide show
  1. package/README.md +4 -4
  2. package/bin/build +1 -1
  3. package/bin/test +1 -1
  4. package/dist/index.d.mts +5226 -0
  5. package/dist/index.mjs +872 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +8 -6
  8. package/src/index.ts +18 -5
  9. package/src/rules/__tests__/importsAndReExportsAtTop.test.ts +3 -1
  10. package/src/rules/__tests__/individualImports.test.ts +3 -1
  11. package/src/rules/__tests__/individualReExports.test.ts +3 -1
  12. package/src/rules/__tests__/multilineUnionTypes.test.ts +75 -0
  13. package/src/rules/__tests__/singleLineImports.test.ts +129 -0
  14. package/src/rules/__tests__/singleLineReExports.test.ts +100 -0
  15. package/src/rules/__tests__/sortedImports.test.ts +37 -1
  16. package/src/rules/__tests__/sortedReExports.test.ts +27 -1
  17. package/src/rules/importsAndReExportsAtTop/CategorizedStatements.ts +2 -2
  18. package/src/rules/importsAndReExportsAtTop/categorizeStatements.ts +7 -6
  19. package/src/rules/importsAndReExportsAtTop/generateSortedText.ts +4 -6
  20. package/src/rules/importsAndReExportsAtTop/getStatementType.ts +1 -1
  21. package/src/rules/importsAndReExportsAtTop/index.ts +5 -5
  22. package/src/rules/importsAndReExportsAtTop/isImportDeclaration.ts +7 -0
  23. package/src/rules/importsAndReExportsAtTop/isReExport.ts +12 -0
  24. package/src/rules/individualImports.ts +4 -3
  25. package/src/rules/individualReExports.ts +8 -9
  26. package/src/rules/multilineUnionTypes/createFix.ts +13 -0
  27. package/src/rules/multilineUnionTypes/index.ts +52 -0
  28. package/src/rules/multilineUnionTypes/isMultiline.ts +6 -0
  29. package/src/rules/singleLineImports/createFix.ts +23 -0
  30. package/src/rules/singleLineImports/formatAttributes.ts +20 -0
  31. package/src/rules/singleLineImports/formatNamed.ts +9 -0
  32. package/src/rules/singleLineImports/formatSpecifiers.ts +32 -0
  33. package/src/rules/singleLineImports/index.ts +34 -0
  34. package/src/rules/singleLineImports/isMultiline.ts +6 -0
  35. package/src/rules/singleLineReExports/createFix.ts +29 -0
  36. package/src/rules/singleLineReExports/index.ts +48 -0
  37. package/src/rules/singleLineReExports/isMultiline.ts +6 -0
  38. package/src/rules/sortedImports/ImportError.ts +4 -1
  39. package/src/rules/sortedImports/ImportGroup.ts +2 -1
  40. package/src/rules/sortedImports/ImportGroupOrder.ts +2 -1
  41. package/src/rules/sortedImports/areSpecifiersSorted.ts +1 -1
  42. package/src/rules/sortedImports/categorizeImport.ts +4 -0
  43. package/src/rules/sortedImports/checkAlphabeticalSorting.ts +1 -1
  44. package/src/rules/sortedImports/createFix/buildSortedCode.ts +2 -1
  45. package/src/rules/sortedImports/createFix/formatNamedImport.ts +3 -2
  46. package/src/rules/sortedImports/createFix/getReplacementRange.ts +3 -3
  47. package/src/rules/sortedImports/createFix/groupImportsByType.ts +1 -0
  48. package/src/rules/sortedImports/createFix/index.ts +10 -11
  49. package/src/rules/sortedImports/createFix/sortImportGroups.ts +2 -1
  50. package/src/rules/sortedImports/getSortKey.ts +8 -2
  51. package/src/rules/sortedImports/index.ts +8 -5
  52. package/src/rules/sortedImports/sortSpecifiersText.ts +4 -3
  53. package/src/rules/sortedReExports/CategorizedReExport.ts +14 -3
  54. package/src/rules/sortedReExports/ReExportError.ts +5 -2
  55. package/src/rules/sortedReExports/ReExportGroup.ts +1 -0
  56. package/src/rules/sortedReExports/ReExportGroupOrder.ts +2 -1
  57. package/src/rules/sortedReExports/areSpecifiersSorted.ts +1 -1
  58. package/src/rules/sortedReExports/categorizeReExport.ts +8 -4
  59. package/src/rules/sortedReExports/categorizeReExports.ts +1 -1
  60. package/src/rules/sortedReExports/checkAlphabeticalSorting.ts +1 -1
  61. package/src/rules/sortedReExports/createFix/buildSortedCode.ts +2 -1
  62. package/src/rules/sortedReExports/createFix/formatNamedReExport.ts +3 -2
  63. package/src/rules/sortedReExports/createFix/getReplacementRange.ts +1 -1
  64. package/src/rules/sortedReExports/createFix/groupReExportsByType.ts +1 -0
  65. package/src/rules/sortedReExports/createFix/index.ts +10 -11
  66. package/src/rules/sortedReExports/createFix/sortExportGroups.ts +2 -1
  67. package/src/rules/sortedReExports/getNamedSpecifiers.ts +1 -1
  68. package/src/rules/sortedReExports/getReExportGroups.ts +1 -1
  69. package/src/rules/sortedReExports/getSortKey.ts +11 -5
  70. package/src/rules/sortedReExports/index.ts +8 -5
  71. package/src/rules/sortedReExports/isNamedReExport.ts +2 -2
  72. package/src/rules/sortedReExports/sortSpecifiersText.ts +4 -3
  73. package/tsdown.config.ts +19 -0
  74. package/dist/index.d.ts +0 -5
  75. package/dist/index.js +0 -855
  76. package/dist/index.js.map +0 -1
  77. package/src/rules/importsAndReExportsAtTop/ReExport.ts +0 -5
  78. /package/src/{rules/sortedReExports → lib}/ReExportDeclaration.ts +0 -0
@@ -2,7 +2,9 @@ import typescript from 'typescript-eslint'
2
2
  import {dedent} from './dedent'
3
3
  import {RuleTester} from 'eslint'
4
4
  import {sortedImports} from '../sortedImports'
5
+ import type {Rule} from 'eslint'
5
6
 
7
+ const rule = sortedImports as unknown as Rule.RuleModule
6
8
  const ruleTester = new RuleTester({
7
9
  languageOptions: {
8
10
  parser: typescript.parser,
@@ -13,7 +15,7 @@ const ruleTester = new RuleTester({
13
15
  },
14
16
  })
15
17
 
16
- ruleTester.run('sorted-imports', sortedImports, {
18
+ ruleTester.run('sorted-imports', rule, {
17
19
  valid: [{
18
20
  code: "import {foo} from 'bar'",
19
21
  }, {
@@ -22,10 +24,14 @@ ruleTester.run('sorted-imports', sortedImports, {
22
24
  code: "import 'bar'",
23
25
  }, {
24
26
  code: "import type {Foo} from 'bar'",
27
+ }, {
28
+ code: "import * as fs from 'fs'",
25
29
  }, {
26
30
  code: dedent`
27
31
  import 'aaa'
28
32
  import 'bbb'
33
+ import * as fs from 'fs'
34
+ import * as path from 'path'
29
35
  import bar from 'bbb'
30
36
  import foo from 'aaa'
31
37
  import {a} from 'aaa'
@@ -187,5 +193,35 @@ ruleTester.run('sorted-imports', sortedImports, {
187
193
  import {basename} from 'path'
188
194
  import {existsSync} from 'fs'
189
195
  `,
196
+ }, {
197
+ code: dedent`
198
+ import * as path from 'path'
199
+ import * as fs from 'fs'
200
+ `,
201
+ errors: [{messageId: 'sortedImports'}, {messageId: 'sortedImports'}],
202
+ output: dedent`
203
+ import * as fs from 'fs'
204
+ import * as path from 'path'
205
+ `,
206
+ }, {
207
+ code: dedent`
208
+ import {foo} from 'bar'
209
+ import * as fs from 'fs'
210
+ `,
211
+ errors: [{messageId: 'wrongGroup'}],
212
+ output: dedent`
213
+ import * as fs from 'fs'
214
+ import {foo} from 'bar'
215
+ `,
216
+ }, {
217
+ code: dedent`
218
+ import foo from 'bar'
219
+ import * as fs from 'fs'
220
+ `,
221
+ errors: [{messageId: 'wrongGroup'}],
222
+ output: dedent`
223
+ import * as fs from 'fs'
224
+ import foo from 'bar'
225
+ `,
190
226
  }],
191
227
  })
@@ -2,7 +2,9 @@ import typescript from 'typescript-eslint'
2
2
  import {dedent} from './dedent'
3
3
  import {RuleTester} from 'eslint'
4
4
  import {sortedReExports} from '../sortedReExports'
5
+ import type {Rule} from 'eslint'
5
6
 
7
+ const rule = sortedReExports as unknown as Rule.RuleModule
6
8
  const ruleTester = new RuleTester({
7
9
  languageOptions: {
8
10
  parser: typescript.parser,
@@ -13,7 +15,7 @@ const ruleTester = new RuleTester({
13
15
  },
14
16
  })
15
17
 
16
- ruleTester.run('sorted-re-exports', sortedReExports, {
18
+ ruleTester.run('sorted-re-exports', rule, {
17
19
  valid: [{
18
20
  code: 'export const foo = 42',
19
21
  }, {
@@ -26,9 +28,13 @@ ruleTester.run('sorted-re-exports', sortedReExports, {
26
28
  code: "export * from 'bar'",
27
29
  }, {
28
30
  code: "export type {Foo} from 'bar'",
31
+ }, {
32
+ code: "export * as ns from 'bar'",
29
33
  }, {
30
34
  code: dedent`
31
35
  export * from 'aaa'
36
+ export * as fs from 'fs'
37
+ export * as path from 'path'
32
38
  export {a} from 'aaa'
33
39
  export {b} from 'bbb'
34
40
  export type {X} from 'xxx'
@@ -190,5 +196,25 @@ ruleTester.run('sorted-re-exports', sortedReExports, {
190
196
  export {basename} from 'path'
191
197
  export {existsSync} from 'fs'
192
198
  `,
199
+ }, {
200
+ code: dedent`
201
+ export * as path from 'path'
202
+ export * as fs from 'fs'
203
+ `,
204
+ errors: [{messageId: 'sortedReExports'}, {messageId: 'sortedReExports'}],
205
+ output: dedent`
206
+ export * as fs from 'fs'
207
+ export * as path from 'path'
208
+ `,
209
+ }, {
210
+ code: dedent`
211
+ export {foo} from 'bar'
212
+ export * as fs from 'fs'
213
+ `,
214
+ errors: [{messageId: 'wrongGroup'}],
215
+ output: dedent`
216
+ export * as fs from 'fs'
217
+ export {foo} from 'bar'
218
+ `,
193
219
  }],
194
220
  })
@@ -1,8 +1,8 @@
1
- import type {ReExport} from './ReExport'
1
+ import type {ReExportDeclaration} from '@lib/ReExportDeclaration'
2
2
  import type {TSESTree} from '@typescript-eslint/types'
3
3
 
4
4
  export interface CategorizedStatements {
5
5
  imports: TSESTree.ImportDeclaration[]
6
- reExports: ReExport[]
6
+ reExports: ReExportDeclaration[]
7
7
  other: TSESTree.Statement[]
8
8
  }
@@ -1,10 +1,11 @@
1
1
  import {getStatementType} from './getStatementType'
2
- import {ReExport} from './ReExport'
2
+ import {isImportDeclaration} from './isImportDeclaration'
3
+ import {isReExport} from './isReExport'
3
4
  import type {CategorizedStatements} from './CategorizedStatements'
4
5
  import type {TSESTree} from '@typescript-eslint/types'
5
6
 
6
7
  export function categorizeStatements(
7
- statements: TSESTree.Statement[],
8
+ statements: TSESTree.ProgramStatement[],
8
9
  ): CategorizedStatements {
9
10
  const result: CategorizedStatements = {
10
11
  imports: [],
@@ -15,10 +16,10 @@ export function categorizeStatements(
15
16
  for (const statement of statements) {
16
17
  const type = getStatementType(statement)
17
18
 
18
- if (type === 'import')
19
- result.imports.push(statement as TSESTree.ImportDeclaration)
20
- else if (type === 're-export')
21
- result.reExports.push(statement as ReExport)
19
+ if (type === 'import' && isImportDeclaration(statement))
20
+ result.imports.push(statement)
21
+ else if (type === 're-export' && isReExport(statement))
22
+ result.reExports.push(statement)
22
23
  else
23
24
  result.other.push(statement)
24
25
  }
@@ -1,18 +1,16 @@
1
- import {Rule} from 'eslint'
2
1
  import type {CategorizedStatements} from './CategorizedStatements'
3
- import type {Node as ESTreeNode} from 'estree'
4
- import type {TSESTree} from '@typescript-eslint/types'
2
+ import type {TSESLint} from '@typescript-eslint/utils'
5
3
 
6
4
  export function generateSortedText(
7
- context: Rule.RuleContext,
5
+ context: TSESLint.RuleContext<'importsAndReExportsAtTop', []>,
8
6
  categories: CategorizedStatements,
9
7
  ): string {
10
- const allStatements: TSESTree.Node[] = [
8
+ const allStatements = [
11
9
  ...categories.imports,
12
10
  ...categories.reExports,
13
11
  ...categories.other,
14
12
  ]
15
13
  return allStatements.map(
16
- node => context.sourceCode.getText(node as ESTreeNode),
14
+ node => context.sourceCode.getText(node),
17
15
  ).join('\n')
18
16
  }
@@ -1,4 +1,4 @@
1
- import {StatementType} from './statementType'
1
+ import type {StatementType} from './statementType'
2
2
  import type {TSESTree} from '@typescript-eslint/types'
3
3
 
4
4
  export function getStatementType(statement: TSESTree.Statement): StatementType {
@@ -2,15 +2,15 @@ import {categorizeStatements} from './categorizeStatements'
2
2
  import {findStatementIndices} from './findStatementIndices'
3
3
  import {generateSortedText} from './generateSortedText'
4
4
  import {hasViolation} from './hasViolation'
5
- import type {Rule} from 'eslint'
6
- import type {TSESTree} from '@typescript-eslint/types'
5
+ import type {TSESLint} from '@typescript-eslint/utils'
7
6
 
8
- export const importsAndReExportsAtTop: Rule.RuleModule = {
7
+ type MessageIds = 'importsAndReExportsAtTop'
8
+
9
+ export const importsAndReExportsAtTop: TSESLint.RuleModule<MessageIds, []> = {
9
10
  meta: {
10
11
  type: 'suggestion',
11
12
  docs: {
12
13
  description: 'Enforce imports and re-exports at the top of the file',
13
- recommended: false,
14
14
  },
15
15
  fixable: 'code',
16
16
  messages: {
@@ -23,7 +23,7 @@ export const importsAndReExportsAtTop: Rule.RuleModule = {
23
23
  create(context) {
24
24
  return {
25
25
  Program(node) {
26
- const statements = node.body as TSESTree.Statement[]
26
+ const statements = node.body
27
27
  const categories = categorizeStatements(statements)
28
28
  const indices = findStatementIndices(statements)
29
29
 
@@ -0,0 +1,7 @@
1
+ import type {TSESTree} from '@typescript-eslint/types'
2
+
3
+ export function isImportDeclaration(
4
+ statement: TSESTree.ProgramStatement,
5
+ ): statement is TSESTree.ImportDeclaration {
6
+ return statement.type === 'ImportDeclaration'
7
+ }
@@ -0,0 +1,12 @@
1
+ import type {ReExportDeclaration} from '@lib/ReExportDeclaration'
2
+ import type {TSESTree} from '@typescript-eslint/types'
3
+
4
+ export function isReExport(
5
+ statement: TSESTree.ProgramStatement,
6
+ ): statement is ReExportDeclaration {
7
+ if (statement.type === 'ExportAllDeclaration')
8
+ return true
9
+ if (statement.type === 'ExportNamedDeclaration')
10
+ return statement.source !== null
11
+ return false
12
+ }
@@ -1,10 +1,11 @@
1
- import type {Rule} from 'eslint'
1
+ import type {TSESLint} from '@typescript-eslint/utils'
2
2
 
3
- export const individualImports: Rule.RuleModule = {
3
+ type MessageIds = 'individualImports'
4
+
5
+ export const individualImports: TSESLint.RuleModule<MessageIds, []> = {
4
6
  meta: {
5
7
  docs: {
6
8
  description: 'Enforce individual imports instead of grouped imports',
7
- recommended: true,
8
9
  },
9
10
  fixable: 'code',
10
11
  messages: {
@@ -1,11 +1,11 @@
1
- import type {Rule} from 'eslint'
2
- import type {TSESTree} from '@typescript-eslint/types'
1
+ import type {TSESLint} from '@typescript-eslint/utils'
3
2
 
4
- export const individualReExports: Rule.RuleModule = {
3
+ type MessageIds = 'individualReExports'
4
+
5
+ export const individualReExports: TSESLint.RuleModule<MessageIds, []> = {
5
6
  meta: {
6
7
  docs: {
7
8
  description: 'Enforce individual exports instead of grouped exports',
8
- recommended: true,
9
9
  },
10
10
  fixable: 'code',
11
11
  messages: {
@@ -17,19 +17,18 @@ export const individualReExports: Rule.RuleModule = {
17
17
  create(context) {
18
18
  return {
19
19
  ExportNamedDeclaration(node) {
20
- const exportNode = node as TSESTree.ExportNamedDeclaration
21
- if (!exportNode.source || exportNode.specifiers.length <= 1)
20
+ if (!node.source || node.specifiers.length <= 1)
22
21
  return
23
22
 
24
23
  context.report({
25
24
  node,
26
25
  messageId: 'individualReExports',
27
26
  fix(fixer) {
28
- const source = exportNode.source!.value
29
- const typeKeyword = exportNode.exportKind === 'type'
27
+ const source = node.source.value
28
+ const typeKeyword = node.exportKind === 'type'
30
29
  ? 'type '
31
30
  : ''
32
- const specifiers = exportNode.specifiers
31
+ const specifiers = node.specifiers
33
32
  .map(s => {
34
33
  const localName = s.local.type === 'Identifier'
35
34
  ? s.local.name
@@ -0,0 +1,13 @@
1
+ import type {TSESLint} from '@typescript-eslint/utils'
2
+ import type {TSESTree} from '@typescript-eslint/utils'
3
+
4
+ export function createFix(
5
+ fixer: TSESLint.RuleFixer,
6
+ node: TSESTree.TSUnionType,
7
+ sourceCode: TSESLint.SourceCode,
8
+ ): TSESLint.RuleFix {
9
+ const types = node.types.map(t => sourceCode.getText(t))
10
+ const formattedTypes = types.map(t => ` | ${t}`).join('\n')
11
+ const result = `\n${formattedTypes}`
12
+ return fixer.replaceText(node, result)
13
+ }
@@ -0,0 +1,52 @@
1
+ import {createFix} from './createFix'
2
+ import {isMultiline} from './isMultiline'
3
+ import type {TSESLint} from '@typescript-eslint/utils'
4
+
5
+ type MessageIds =
6
+ | 'singleLine'
7
+ | 'missingPipes'
8
+
9
+ export const multilineUnionTypes: TSESLint.RuleModule<MessageIds, []> = {
10
+ meta: {
11
+ docs: {
12
+ description: 'Enforce union types with multiple members to be on multiple lines',
13
+ },
14
+ fixable: 'code',
15
+ messages: {
16
+ singleLine: 'Union types with multiple members should be on multiple lines',
17
+ missingPipes: 'Multiline union types should have leading pipes on each member',
18
+ },
19
+ schema: [],
20
+ type: 'layout',
21
+ },
22
+
23
+ create(context) {
24
+ return {
25
+ TSUnionType(node) {
26
+ if (node.types.length < 2)
27
+ return
28
+
29
+ const sourceCode = context.sourceCode
30
+ const text = sourceCode.getText(node)
31
+
32
+ if (text.trim().startsWith('|'))
33
+ return
34
+
35
+ if (!isMultiline(node)) {
36
+ context.report({
37
+ node,
38
+ messageId: 'singleLine',
39
+ fix: fixer => createFix(fixer, node, sourceCode),
40
+ })
41
+ return
42
+ }
43
+
44
+ context.report({
45
+ node,
46
+ messageId: 'missingPipes',
47
+ fix: fixer => createFix(fixer, node, sourceCode),
48
+ })
49
+ },
50
+ }
51
+ },
52
+ }
@@ -0,0 +1,6 @@
1
+ import type {TSESTree} from '@typescript-eslint/types'
2
+
3
+ export function isMultiline(unionType: TSESTree.TSUnionType) {
4
+ const {start, end} = unionType.loc ?? {}
5
+ return start?.line !== end?.line
6
+ }
@@ -0,0 +1,23 @@
1
+ import {formatAttributes} from './formatAttributes'
2
+ import {formatSpecifiers} from './formatSpecifiers'
3
+ import type {TSESLint} from '@typescript-eslint/utils'
4
+ import type {TSESTree} from '@typescript-eslint/utils'
5
+
6
+ export function createFix(
7
+ fixer: TSESLint.RuleFixer,
8
+ declaration: TSESTree.ImportDeclaration,
9
+ sourceCode: TSESLint.SourceCode,
10
+ ): TSESLint.RuleFix {
11
+ const source = declaration.source.value
12
+ const prefix = declaration.importKind === 'type' ? 'import type ' : 'import '
13
+ const specifiers = formatSpecifiers(declaration, sourceCode)
14
+ const attributes = formatAttributes(declaration.attributes)
15
+
16
+ if (specifiers === '') {
17
+ const result = `${prefix}'${source}'${attributes}`
18
+ return fixer.replaceText(declaration, result)
19
+ }
20
+
21
+ const result = `${prefix}${specifiers} from '${source}'${attributes}`
22
+ return fixer.replaceText(declaration, result)
23
+ }
@@ -0,0 +1,20 @@
1
+ import type {TSESTree} from '@typescript-eslint/types'
2
+
3
+ export function formatAttributes(
4
+ attributes: readonly TSESTree.ImportAttribute[],
5
+ ): string {
6
+ if (attributes.length === 0)
7
+ return ''
8
+
9
+ const formatted = attributes.map(
10
+ attr => {
11
+ const key = attr.key.type === 'Identifier'
12
+ ? attr.key.name
13
+ : attr.key.value
14
+ const value = attr.value.value
15
+ return `${key}: '${value}'`
16
+ },
17
+ ).join(', ')
18
+
19
+ return ` with {${formatted}}`
20
+ }
@@ -0,0 +1,9 @@
1
+ import type {TSESLint} from '@typescript-eslint/utils'
2
+ import type {TSESTree} from '@typescript-eslint/utils'
3
+
4
+ export function formatNamed(
5
+ specifiers: TSESTree.ImportSpecifier[],
6
+ sourceCode: TSESLint.SourceCode,
7
+ ): string {
8
+ return specifiers.map(s => sourceCode.getText(s)).join(', ')
9
+ }
@@ -0,0 +1,32 @@
1
+ import {formatNamed} from './formatNamed'
2
+ import type {TSESLint} from '@typescript-eslint/utils'
3
+ import type {TSESTree} from '@typescript-eslint/utils'
4
+
5
+ export function formatSpecifiers(
6
+ declaration: TSESTree.ImportDeclaration,
7
+ sourceCode: TSESLint.SourceCode,
8
+ ): string {
9
+ const defaultSpecifier = declaration.specifiers.find(
10
+ (s): s is TSESTree.ImportDefaultSpecifier => s.type === 'ImportDefaultSpecifier',
11
+ )
12
+ const namespaceSpecifier = declaration.specifiers.find(
13
+ (s): s is TSESTree.ImportNamespaceSpecifier => s.type === 'ImportNamespaceSpecifier',
14
+ )
15
+ const namedSpecifiers = declaration.specifiers.filter(
16
+ (s): s is TSESTree.ImportSpecifier => s.type === 'ImportSpecifier',
17
+ )
18
+
19
+ if (namespaceSpecifier)
20
+ return `* as ${namespaceSpecifier.local.name}`
21
+
22
+ if (defaultSpecifier && namedSpecifiers.length > 0)
23
+ return `${defaultSpecifier.local.name}, {${formatNamed(namedSpecifiers, sourceCode)}}`
24
+
25
+ if (defaultSpecifier)
26
+ return defaultSpecifier.local.name
27
+
28
+ if (namedSpecifiers.length === 0)
29
+ return ''
30
+
31
+ return `{${formatNamed(namedSpecifiers, sourceCode)}}`
32
+ }
@@ -0,0 +1,34 @@
1
+ import {createFix} from './createFix'
2
+ import {isMultiline} from './isMultiline'
3
+ import type {TSESLint} from '@typescript-eslint/utils'
4
+
5
+ type MessageIds = 'multiline'
6
+
7
+ export const singleLineImports: TSESLint.RuleModule<MessageIds, []> = {
8
+ meta: {
9
+ docs: {
10
+ description: 'Enforce imports to be on a single line',
11
+ },
12
+ fixable: 'code',
13
+ messages: {
14
+ multiline: 'Import should be on a single line',
15
+ },
16
+ schema: [],
17
+ type: 'layout',
18
+ },
19
+
20
+ create(context) {
21
+ return {
22
+ ImportDeclaration(node) {
23
+ if (!isMultiline(node))
24
+ return
25
+
26
+ context.report({
27
+ node,
28
+ messageId: 'multiline',
29
+ fix: fixer => createFix(fixer, node, context.sourceCode),
30
+ })
31
+ },
32
+ }
33
+ },
34
+ }
@@ -0,0 +1,6 @@
1
+ import type {TSESTree} from '@typescript-eslint/types'
2
+
3
+ export function isMultiline(declaration: TSESTree.ImportDeclaration) {
4
+ const {start, end} = declaration.loc ?? {}
5
+ return start?.line !== end?.line
6
+ }
@@ -0,0 +1,29 @@
1
+ import {formatAttributes} from '../singleLineImports/formatAttributes'
2
+ import type {ReExportDeclaration} from '@lib/ReExportDeclaration'
3
+ import type {TSESLint} from '@typescript-eslint/utils'
4
+
5
+ export function createFix(
6
+ fixer: TSESLint.RuleFixer,
7
+ declaration: ReExportDeclaration,
8
+ sourceCode: TSESLint.SourceCode,
9
+ ): TSESLint.RuleFix {
10
+ const source = declaration.source!.value
11
+
12
+ if (declaration.type === 'ExportAllDeclaration') {
13
+ const exported = declaration.exported
14
+ ? `* as ${sourceCode.getText(declaration.exported)}`
15
+ : '*'
16
+ const attributes = formatAttributes(declaration.attributes)
17
+ const result = `export ${exported} from '${source}'${attributes}`
18
+ return fixer.replaceText(declaration, result)
19
+ }
20
+
21
+ const prefix = declaration.exportKind === 'type' ? 'export type ' : 'export '
22
+ const specifiers = declaration.specifiers.map(
23
+ s => sourceCode.getText(s),
24
+ ).join(', ')
25
+ const attributes = formatAttributes(declaration.attributes)
26
+ const result = `${prefix}{${specifiers}} from '${source}'${attributes}`
27
+
28
+ return fixer.replaceText(declaration, result)
29
+ }
@@ -0,0 +1,48 @@
1
+ import {createFix} from './createFix'
2
+ import {isMultiline} from './isMultiline'
3
+ import type {ReExportDeclaration} from '@lib/ReExportDeclaration'
4
+ import type {TSESLint} from '@typescript-eslint/utils'
5
+ import type {TSESTree} from '@typescript-eslint/utils'
6
+
7
+ type MessageIds = 'multiline'
8
+
9
+ export const singleLineReExports: TSESLint.RuleModule<MessageIds, []> = {
10
+ meta: {
11
+ docs: {
12
+ description: 'Enforce re-exports to be on a single line',
13
+ },
14
+ fixable: 'code',
15
+ messages: {
16
+ multiline: 'Re-export should be on a single line',
17
+ },
18
+ schema: [],
19
+ type: 'layout',
20
+ },
21
+
22
+ create(context) {
23
+ const checkDeclaration = (
24
+ node: TSESTree.Node,
25
+ declaration: ReExportDeclaration,
26
+ ) => {
27
+ // Skip local exports (only process re-exports with a source)
28
+ if (!declaration.source)
29
+ return
30
+
31
+ if (!isMultiline(declaration))
32
+ return
33
+
34
+ context.report({
35
+ node,
36
+ messageId: 'multiline',
37
+ fix: fixer => createFix(fixer, declaration, context.sourceCode),
38
+ })
39
+ }
40
+
41
+ return {
42
+ ExportNamedDeclaration: node =>
43
+ checkDeclaration(node, node),
44
+ ExportAllDeclaration: node =>
45
+ checkDeclaration(node, node),
46
+ }
47
+ },
48
+ }
@@ -0,0 +1,6 @@
1
+ import type {ReExportDeclaration} from '@lib/ReExportDeclaration'
2
+
3
+ export function isMultiline(declaration: ReExportDeclaration) {
4
+ const {start, end} = declaration.loc ?? {}
5
+ return start?.line !== end?.line
6
+ }
@@ -2,5 +2,8 @@ import type {TSESTree} from '@typescript-eslint/types'
2
2
 
3
3
  export interface ImportError {
4
4
  node: TSESTree.ImportDeclaration
5
- messageId: 'sortedImports' | 'sortedNames' | 'wrongGroup'
5
+ messageId:
6
+ | 'sortedImports'
7
+ | 'sortedNames'
8
+ | 'wrongGroup'
6
9
  }
@@ -1,5 +1,6 @@
1
1
  export type ImportGroup =
2
- | 'side-effect'
3
2
  | 'default'
4
3
  | 'named'
4
+ | 'namespace'
5
+ | 'side-effect'
5
6
  | 'type'
@@ -1,7 +1,8 @@
1
- import {ImportGroup} from './ImportGroup'
1
+ import type {ImportGroup} from './ImportGroup'
2
2
 
3
3
  export const importGroupOrder: ImportGroup[] = [
4
4
  'side-effect',
5
+ 'namespace',
5
6
  'default',
6
7
  'named',
7
8
  'type',
@@ -1,4 +1,4 @@
1
- import {compare} from '../../lib/compare'
1
+ import {compare} from '@lib/compare'
2
2
  import {getSpecifierName} from './getSpecifierName'
3
3
  import type {TSESTree} from '@typescript-eslint/types'
4
4
 
@@ -10,6 +10,10 @@ export function categorizeImport(declaration: TSESTree.ImportDeclaration): Impor
10
10
  if (declaration.specifiers.length === 0)
11
11
  return 'side-effect'
12
12
 
13
+ // Example: import * as fs from 'module'
14
+ if (declaration.specifiers.some(s => s.type === 'ImportNamespaceSpecifier'))
15
+ return 'namespace'
16
+
13
17
  // Example: import value from 'module'
14
18
  if (declaration.specifiers.some(s => s.type === 'ImportDefaultSpecifier'))
15
19
  return 'default'
@@ -1,4 +1,4 @@
1
- import {compare} from '../../lib/compare'
1
+ import {compare} from '@lib/compare'
2
2
  import {importGroupOrder} from './ImportGroupOrder'
3
3
  import type {CategorizedImport} from './CategorizedImport'
4
4
  import type {ImportError} from './ImportError'