Skip to content

Commit 222877c

Browse files
committed
feat: port eslint-plugin-barrel-files
This ports the rules from `eslint-plugin-barrel-files` into the package.
1 parent f193e73 commit 222877c

16 files changed

+1067
-0
lines changed

LICENSE

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License (MIT)
22

33
Copyright (c) 2015 Ben Mosher
4+
Copyright (c) 2020 modern-webdev
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

docs/rules/avoid-barrel-files.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# import-x/avoid-barrel-files
2+
3+
This rule disallows _authoring_ barrel files in your project.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
export { foo } from 'foo'
11+
export { bar } from 'bar'
12+
export { baz } from 'baz'
13+
```
14+
15+
## Options
16+
17+
This rule has the following options, with these defaults:
18+
19+
```js
20+
"import-x/avoid-barrel-files": ["error", {
21+
"amountOfExportsToConsiderModuleAsBarrel": 5
22+
}]
23+
```
24+
25+
### `amountOfExportsToConsiderModuleAsBarrel`
26+
27+
This option sets the number of exports that will be considered a barrel file. Anything over will trigger the rule.
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# import-x/avoid-importing-barrel-files
2+
3+
This rule aims to avoid importing barrel files that lead to loading large module graphs. This rule is different from the `avoid-barrel-files` rule, which lints against _authoring_ barrel files. This rule lints against _importing_ barrel files.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
// If `foo` is a barrel file leading to a module graph of more than 20 modules
11+
export { foo } from 'foo'
12+
```
13+
14+
## Options
15+
16+
This rule has the following options, with these defaults:
17+
18+
```js
19+
"import-x/avoid-importing-barrel-files": ["error", {
20+
allowList: [],
21+
maxModuleGraphSizeAllowed: 20,
22+
amountOfExportsToConsiderModuleAsBarrel: 3,
23+
debug: false,
24+
exportConditions: ["node", "import"],
25+
mainFields: ["module", "browser", "main"],
26+
extensions: [".js", ".ts", ".tsx", ".jsx", ".json", ".node"],
27+
}]
28+
```
29+
30+
### `allowList`
31+
32+
List of modules from which to allow barrel files being imported.
33+
34+
### `maxModuleGraphSizeAllowed`
35+
36+
Maximum allowed module graph size. If an imported module results in loading more than this many modules, it will be considered a barrel file.
37+
38+
### `amountOfExportsToConsiderModuleAsBarrel`
39+
40+
Amount of exports to consider a module as a barrel file.
41+
42+
### `exportConditions`
43+
44+
Export conditions to use when resolving modules.
45+
46+
### `mainFields`
47+
48+
Main fields to use when resolving modules.
49+
50+
### `extensions`
51+
52+
Extensions to use when resolving modules.
53+
54+
### `tsconfig`
55+
56+
TSConfig options to pass to the underlying resolver when resolving modules.
57+
58+
This can be useful when resolving project references in TypeScript.
59+
60+
### `alias`
61+
62+
The rule can accept an `alias` option whose value can be an object that matches Webpack's [resolve.alias](https://webpack.js.org/configuration/resolve/) configuration.
63+
64+
```js
65+
// .eslintrc.cjs
66+
const path = require('path')
67+
68+
export default {
69+
// ...
70+
rules: {
71+
'barrel-files/avoid-importing-barrel-files': [
72+
2,
73+
{
74+
alias: {
75+
// "@/foo/bar.js" => "./src/foo/bar.js"
76+
'@': [path.resolve('.', 'src')],
77+
},
78+
},
79+
],
80+
},
81+
}
82+
```

docs/rules/avoid-namespace-import.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# import-x/avoid-namespace-import
2+
3+
This rule forbids the use of namespace imports as they can lead to unused imports and prevent treeshaking.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
import * as foo from 'foo'
11+
```
12+
13+
## Options
14+
15+
This rule has the following options, with these defaults:
16+
17+
```js
18+
"import-x/avoid-barrel-files": ["error", {
19+
allowList: []
20+
}]
21+
```
22+
23+
### `allowList`
24+
25+
A list of namespace imports that are allowed.

docs/rules/avoid-re-export-all.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# import-x/avoid-re-export-all
2+
3+
This rule forbids exporting `*` from a module as it can lead to unused imports and prevent treeshaking.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
export * from 'foo'
11+
export * as foo from 'foo'
12+
```

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"@typescript-eslint/utils": "^8.31.0",
7575
"comment-parser": "^1.4.1",
7676
"debug": "^4.4.0",
77+
"eslint-barrel-file-utils": "^0.0.11",
7778
"eslint-import-resolver-node": "^0.3.9",
7879
"get-tsconfig": "^4.10.0",
7980
"is-glob": "^4.0.3",

src/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import warnings from './config/warnings.js'
2020
import { meta } from './meta.js'
2121
import { createNodeResolver } from './node-resolver.js'
2222
import { cjsRequire } from './require.js'
23+
import avoidBarrelFiles from './rules/avoid-barrel-files.js'
24+
import avoidImportingBarrelFiles from './rules/avoid-importing-barrel-files.js'
25+
import avoidNamespaceImport from './rules/avoid-namespace-import.js'
26+
import avoidReExportAll from './rules/avoid-re-export-all.js'
2327
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style.js'
2428
import default_ from './rules/default.js'
2529
import dynamicImportChunkname from './rules/dynamic-import-chunkname.js'
@@ -129,6 +133,12 @@ const rules = {
129133

130134
// deprecated aliases to rules
131135
'imports-first': importsFirst,
136+
137+
// barrel files
138+
'avoid-barrel-files': avoidBarrelFiles,
139+
'avoid-importing-barrel-files': avoidImportingBarrelFiles,
140+
'avoid-namespace-import': avoidNamespaceImport,
141+
'avoid-re-export-all': avoidReExportAll,
132142
} satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>
133143

134144
const configs = {

src/rules/avoid-barrel-files.ts

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { createRule } from '../utils/index.js'
2+
3+
export interface Options {
4+
amountOfExportsToConsiderModuleAsBarrel: number
5+
}
6+
7+
export type MessageId = 'avoidBarrel'
8+
9+
const defaultOptions: Options = {
10+
amountOfExportsToConsiderModuleAsBarrel: 3,
11+
}
12+
13+
export default createRule<[Options?], MessageId>({
14+
name: 'avoid-barrel-files',
15+
meta: {
16+
type: 'suggestion',
17+
docs: {
18+
description: 'Forbid authoring of barrel files.',
19+
category: 'Performance',
20+
recommended: true,
21+
},
22+
schema: [
23+
{
24+
type: 'object',
25+
properties: {
26+
amountOfExportsToConsiderModuleAsBarrel: {
27+
type: 'number',
28+
description:
29+
'Minimum amount of exports to consider module as barrelfile',
30+
default: 3,
31+
},
32+
},
33+
additionalProperties: false,
34+
},
35+
],
36+
messages: {
37+
avoidBarrel:
38+
'Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused.',
39+
},
40+
},
41+
defaultOptions: [defaultOptions],
42+
create(context) {
43+
const options = context.options[0] || defaultOptions
44+
const amountOfExportsToConsiderModuleAsBarrel =
45+
options.amountOfExportsToConsiderModuleAsBarrel
46+
47+
return {
48+
Program(node) {
49+
let declarations = 0
50+
let exports = 0
51+
52+
for (const n of node.body) {
53+
if (n.type === 'VariableDeclaration') {
54+
declarations += n.declarations.length
55+
}
56+
57+
if (
58+
n.type === 'FunctionDeclaration' ||
59+
n.type === 'ClassDeclaration' ||
60+
n.type === 'TSTypeAliasDeclaration' ||
61+
n.type === 'TSInterfaceDeclaration'
62+
) {
63+
declarations += 1
64+
}
65+
66+
if (n.type === 'ExportNamedDeclaration') {
67+
exports += n.specifiers.length
68+
}
69+
70+
if (n.type === 'ExportAllDeclaration' && n?.exportKind !== 'type') {
71+
exports += 1
72+
}
73+
74+
if (n.type === 'ExportDefaultDeclaration') {
75+
if (
76+
n.declaration.type === 'FunctionDeclaration' ||
77+
n.declaration.type === 'CallExpression'
78+
) {
79+
declarations += 1
80+
} else if (n.declaration.type === 'ObjectExpression') {
81+
exports += n.declaration.properties.length
82+
} else {
83+
exports += 1
84+
}
85+
}
86+
}
87+
88+
if (
89+
exports > declarations &&
90+
exports > amountOfExportsToConsiderModuleAsBarrel
91+
) {
92+
context.report({
93+
node,
94+
messageId: 'avoidBarrel',
95+
})
96+
}
97+
},
98+
}
99+
},
100+
})

0 commit comments

Comments
 (0)