Skip to content

feat: port eslint-plugin-barrel-files #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
The MIT License (MIT)

Copyright (c) 2015 Ben Mosher
Copyright (c) 2020 modern-webdev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
27 changes: 27 additions & 0 deletions docs/rules/avoid-barrel-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# import-x/avoid-barrel-files

This rule disallows _authoring_ barrel files in your project.

## Rule Details

Examples of **incorrect** code for this rule:

```js
export { foo } from 'foo'
export { bar } from 'bar'
export { baz } from 'baz'
```

## Options

This rule has the following options, with these defaults:

```js
"import-x/avoid-barrel-files": ["error", {
"amountOfExportsToConsiderModuleAsBarrel": 5
}]
```

### `amountOfExportsToConsiderModuleAsBarrel`

This option sets the number of exports that will be considered a barrel file. Anything over will trigger the rule.
87 changes: 87 additions & 0 deletions docs/rules/avoid-importing-barrel-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# import-x/avoid-importing-barrel-files

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.

## Rule Details

Examples of **incorrect** code for this rule:

```js
// If `foo` is a barrel file leading to a module graph of more than 20 modules
export { foo } from 'foo'
```

## Options

This rule has the following options, with these defaults:

```js
"import-x/avoid-importing-barrel-files": ["error", {
allowList: [],
maxModuleGraphSizeAllowed: 20,
amountOfExportsToConsiderModuleAsBarrel: 3,
exportConditions: ["node", "import"],
mainFields: ["module", "browser", "main"],
extensions: [".js", ".ts", ".tsx", ".jsx", ".json", ".node"],
}]
```

### `allowList`

List of modules from which to allow barrel files being imported.

### `maxModuleGraphSizeAllowed`

Maximum allowed module graph size. If an imported module results in loading more than this many modules, it will be considered a barrel file.

### `amountOfExportsToConsiderModuleAsBarrel`

Amount of exports to consider a module as a barrel file.

### `exportConditions`

Export conditions to use when resolving modules.

### `mainFields`

Main fields to use when resolving modules.

### `extensions`

Extensions to use when resolving modules.

### `tsconfig`

TSConfig options to pass to the underlying resolver when resolving modules.

This can be useful when resolving project references in TypeScript.

### `alias`

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.

```js
// eslint.config.js
import path from 'node:path'
import { defineConfig } from 'eslint/config'
import importPlugin from 'eslint-plugin-import-x'

export default defineConfig([
{
plugins: {
'import-x': importPlugin,
},
rules: {
'import-x/avoid-importing-barrel-files': [
'error',
{
alias: {
// "@/foo/bar.js" => "./src/foo/bar.js"
'@': [path.resolve('.', 'src')],
},
},
],
},
},
])
```
25 changes: 25 additions & 0 deletions docs/rules/avoid-namespace-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# import-x/avoid-namespace-import

This rule forbids the use of namespace imports as they can lead to unused imports and prevent treeshaking.

## Rule Details

Examples of **incorrect** code for this rule:

```js
import * as foo from 'foo'
```

## Options

This rule has the following options, with these defaults:

```js
"import-x/avoid-namespace-import": ["error", {
allowList: []
}]
```

### `allowList`

A list of namespace imports that are allowed.
12 changes: 12 additions & 0 deletions docs/rules/avoid-re-export-all.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# import-x/avoid-re-export-all

This rule forbids exporting `*` from a module as it can lead to unused imports and prevent treeshaking.

## Rule Details

Examples of **incorrect** code for this rule:

```js
export * from 'foo'
export * as foo from 'foo'
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@typescript-eslint/utils": "^8.31.0",
"comment-parser": "^1.4.1",
"debug": "^4.4.0",
"eslint-barrel-file-utils": "^0.0.11",
"eslint-import-resolver-node": "^0.3.9",
"get-tsconfig": "^4.10.0",
"is-glob": "^4.0.3",
Expand Down
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import warnings from './config/warnings.js'
import { meta } from './meta.js'
import { createNodeResolver } from './node-resolver.js'
import { cjsRequire } from './require.js'
import avoidBarrelFiles from './rules/avoid-barrel-files.js'
import avoidImportingBarrelFiles from './rules/avoid-importing-barrel-files.js'
import avoidNamespaceImport from './rules/avoid-namespace-import.js'
import avoidReExportAll from './rules/avoid-re-export-all.js'
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style.js'
import default_ from './rules/default.js'
import dynamicImportChunkname from './rules/dynamic-import-chunkname.js'
Expand Down Expand Up @@ -129,6 +133,12 @@ const rules = {

// deprecated aliases to rules
'imports-first': importsFirst,

// barrel files
'avoid-barrel-files': avoidBarrelFiles,
'avoid-importing-barrel-files': avoidImportingBarrelFiles,
'avoid-namespace-import': avoidNamespaceImport,
'avoid-re-export-all': avoidReExportAll,
} satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>

const configs = {
Expand Down
100 changes: 100 additions & 0 deletions src/rules/avoid-barrel-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { createRule } from '../utils/index.js'

export interface Options {
amountOfExportsToConsiderModuleAsBarrel: number
}

export type MessageId = 'avoidBarrel'

const defaultOptions: Options = {
amountOfExportsToConsiderModuleAsBarrel: 3,
}

export default createRule<[Options?], MessageId>({
name: 'avoid-barrel-files',
meta: {
type: 'suggestion',
docs: {
description: 'Forbid authoring of barrel files.',
category: 'Performance',
recommended: true,
},
schema: [
{
type: 'object',
properties: {
amountOfExportsToConsiderModuleAsBarrel: {
type: 'number',
description:
'Minimum amount of exports to consider module as barrelfile',
default: 3,
},
},
additionalProperties: false,
},
],
messages: {
avoidBarrel:
'Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused.',
},
},
defaultOptions: [defaultOptions],
create(context) {
const options = context.options[0] || defaultOptions
const amountOfExportsToConsiderModuleAsBarrel =
options.amountOfExportsToConsiderModuleAsBarrel

return {
Program(node) {
let declarations = 0
let exports = 0

for (const n of node.body) {
if (n.type === 'VariableDeclaration') {
declarations += n.declarations.length
}

if (
n.type === 'FunctionDeclaration' ||
n.type === 'ClassDeclaration' ||
n.type === 'TSTypeAliasDeclaration' ||
n.type === 'TSInterfaceDeclaration'
) {
declarations += 1
}

if (n.type === 'ExportNamedDeclaration') {
exports += n.specifiers.length
}

if (n.type === 'ExportAllDeclaration' && n?.exportKind !== 'type') {
exports += 1
}

if (n.type === 'ExportDefaultDeclaration') {
if (
n.declaration.type === 'FunctionDeclaration' ||
n.declaration.type === 'CallExpression'
) {
declarations += 1
} else if (n.declaration.type === 'ObjectExpression') {
exports += n.declaration.properties.length
} else {
exports += 1
}
}
}

if (
exports > declarations &&
exports > amountOfExportsToConsiderModuleAsBarrel
) {
context.report({
node,
messageId: 'avoidBarrel',
})
}
},
}
},
})
Loading
Loading