From 222877ca418092b7815f989b0185bcb85a609a28 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:17:56 +0100 Subject: [PATCH 1/8] feat: port eslint-plugin-barrel-files This ports the rules from `eslint-plugin-barrel-files` into the package. --- LICENSE | 1 + docs/rules/avoid-barrel-files.md | 27 ++ docs/rules/avoid-importing-barrel-files.md | 82 +++++ docs/rules/avoid-namespace-import.md | 25 ++ docs/rules/avoid-re-export-all.md | 12 + package.json | 1 + src/index.ts | 10 + src/rules/avoid-barrel-files.ts | 100 +++++++ src/rules/avoid-importing-barrel-files.ts | 333 +++++++++++++++++++++ src/rules/avoid-namespace-import.ts | 63 ++++ src/rules/avoid-re-export-all.ts | 33 ++ test/rules/avoid-barrel-files-ts.spec.ts | 61 ++++ test/rules/avoid-barrel-files.spec.ts | 109 +++++++ test/rules/avoid-namespace-import.spec.ts | 41 +++ test/rules/avoid-re-export-all.spec.ts | 29 ++ yarn.lock | 140 +++++++++ 16 files changed, 1067 insertions(+) create mode 100644 docs/rules/avoid-barrel-files.md create mode 100644 docs/rules/avoid-importing-barrel-files.md create mode 100644 docs/rules/avoid-namespace-import.md create mode 100644 docs/rules/avoid-re-export-all.md create mode 100644 src/rules/avoid-barrel-files.ts create mode 100644 src/rules/avoid-importing-barrel-files.ts create mode 100644 src/rules/avoid-namespace-import.ts create mode 100644 src/rules/avoid-re-export-all.ts create mode 100644 test/rules/avoid-barrel-files-ts.spec.ts create mode 100644 test/rules/avoid-barrel-files.spec.ts create mode 100644 test/rules/avoid-namespace-import.spec.ts create mode 100644 test/rules/avoid-re-export-all.spec.ts diff --git a/LICENSE b/LICENSE index c6ade2a4..de415124 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/docs/rules/avoid-barrel-files.md b/docs/rules/avoid-barrel-files.md new file mode 100644 index 00000000..76e934fb --- /dev/null +++ b/docs/rules/avoid-barrel-files.md @@ -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. diff --git a/docs/rules/avoid-importing-barrel-files.md b/docs/rules/avoid-importing-barrel-files.md new file mode 100644 index 00000000..c390f0df --- /dev/null +++ b/docs/rules/avoid-importing-barrel-files.md @@ -0,0 +1,82 @@ +# 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, + debug: false, + 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 +// .eslintrc.cjs +const path = require('path') + +export default { + // ... + rules: { + 'barrel-files/avoid-importing-barrel-files': [ + 2, + { + alias: { + // "@/foo/bar.js" => "./src/foo/bar.js" + '@': [path.resolve('.', 'src')], + }, + }, + ], + }, +} +``` diff --git a/docs/rules/avoid-namespace-import.md b/docs/rules/avoid-namespace-import.md new file mode 100644 index 00000000..24c12ff2 --- /dev/null +++ b/docs/rules/avoid-namespace-import.md @@ -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-barrel-files": ["error", { + allowList: [] +}] +``` + +### `allowList` + +A list of namespace imports that are allowed. diff --git a/docs/rules/avoid-re-export-all.md b/docs/rules/avoid-re-export-all.md new file mode 100644 index 00000000..44d3af54 --- /dev/null +++ b/docs/rules/avoid-re-export-all.md @@ -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' +``` diff --git a/package.json b/package.json index 34c731c3..1cf776d6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index 720c030e..3f77886e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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' @@ -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> const configs = { diff --git a/src/rules/avoid-barrel-files.ts b/src/rules/avoid-barrel-files.ts new file mode 100644 index 00000000..5a127529 --- /dev/null +++ b/src/rules/avoid-barrel-files.ts @@ -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', + }) + } + }, + } + }, +}) diff --git a/src/rules/avoid-importing-barrel-files.ts b/src/rules/avoid-importing-barrel-files.ts new file mode 100644 index 00000000..269a64fb --- /dev/null +++ b/src/rules/avoid-importing-barrel-files.ts @@ -0,0 +1,333 @@ +import { readFileSync } from 'node:fs' +import { builtinModules } from 'node:module' +import path from 'node:path' + +import { + count_module_graph_size, + is_barrel_file, +} from 'eslint-barrel-file-utils/index.cjs' +import { ResolverFactory } from 'unrs-resolver' +import type { ResolveResult } from 'unrs-resolver' + +import { createRule } from '../utils/index.js' + +export interface Options { + allowList: string[] + maxModuleGraphSizeAllowed: number + amountOfExportsToConsiderModuleAsBarrel: number + debug: boolean + exportConditions: string[] + mainFields: string[] + extensions: string[] + tsconfig?: { + configFile: string + references: string[] + } + alias?: Record> +} + +export type MessageId = 'avoidImport' + +const defaultOptions: Options = { + allowList: [], + maxModuleGraphSizeAllowed: 20, + amountOfExportsToConsiderModuleAsBarrel: 3, + debug: false, + exportConditions: ['node', 'import'], + mainFields: ['module', 'browser', 'main'], + extensions: ['.js', '.ts', '.tsx', '.jsx', '.json', '.node'], + tsconfig: undefined, + alias: undefined, +} + +const cache: Record< + string, + { isBarrelFile: boolean; moduleGraphSize: number } +> = {} + +/** + * @param {string} specifier + * @returns {boolean} + */ +const isBareModuleSpecifier = (specifier: string): boolean => + !!specifier?.replace(/'/g, '')[0].match(/[@a-zA-Z]/g) + +// custom error class to emulate oxc_resolver ResolveError enum. +// `errorVariant` can be equal to a `ResolveError` enum variant. +class ResolveError extends Error { + public errorVariant: string | null + + constructor(errorVariant: string | null = null, message: string = '') { + super(message) + this.errorVariant = errorVariant + this.message = message + } +} + +export default createRule<[Options?], MessageId>({ + name: 'avoid-importing-barrel-files', + meta: { + type: 'problem', + docs: { + description: 'Forbid importing barrel files.', + recommended: true, + }, + schema: [ + { + type: 'object', + properties: { + allowList: { + type: 'array', + description: 'List of modules from which to allow barrel files', + default: [], + uniqueItems: true, + items: { + type: 'string', + }, + }, + maxModuleGraphSizeAllowed: { + type: 'number', + description: 'Maximum allowed module graph size', + default: 20, + }, + amountOfExportsToConsiderModuleAsBarrel: { + type: 'number', + description: + 'Amount of exports to consider a module as barrel file', + default: 3, + }, + debug: { + type: 'boolean', + description: 'Enabling debug logging', + default: false, + }, + exportConditions: { + type: 'array', + description: + 'Export conditions to use to resolve bare module specifiers', + default: [], + uniqueItems: true, + items: { + type: 'string', + }, + }, + mainFields: { + type: 'array', + description: 'Main fields to use to resolve modules', + default: [], + uniqueItems: true, + items: { + type: 'string', + }, + }, + extensions: { + type: 'array', + description: 'Extensions to use to resolve modules', + default: [], + uniqueItems: true, + items: { + type: 'string', + }, + }, + // schema to match oxc-resolver's TsconfigOptions + tsconfig: { + type: 'object', + description: 'Options to TsconfigOptions', + properties: { + configFile: { + type: 'string', + description: 'Relative path to the configuration file', + }, + references: { + type: 'array', + description: 'Typescript Project References', + items: { + type: 'string', + }, + }, + }, + }, + // NapiResolveOptions.alias + alias: { + type: 'object', + description: 'Webpack aliases used in imports or requires', + }, + }, + }, + ], + messages: { + avoidImport: + 'The imported module "{{specifier}}" is a barrel file, which leads to importing a module graph of {{amount}} modules, which exceeds the maximum allowed size of {{maxModuleGraphSizeAllowed}} modules', + }, + }, + defaultOptions: [defaultOptions], + create(context) { + const options = context.options[0] || defaultOptions + const maxModuleGraphSizeAllowed = options.maxModuleGraphSizeAllowed + const debug = options.debug + const amountOfExportsToConsiderModuleAsBarrel = + options.amountOfExportsToConsiderModuleAsBarrel + const exportConditions = options.exportConditions + const mainFields = options.mainFields + const extensions = options.extensions + const tsconfig = options.tsconfig + const alias = options.alias + + const resolutionOptions = { + exportConditions, + mainFields, + extensions, + tsconfig, + alias, + } + + const resolver = new ResolverFactory({ + tsconfig, + alias, + conditionNames: exportConditions, + mainFields, + extensions, + }) + + return { + ImportDeclaration(node) { + const moduleSpecifier = node.source.value + const currentFileName = context.filename + + if (options?.allowList?.includes(moduleSpecifier)) { + return + } + + if (node?.importKind === 'type') { + return + } + + if (builtinModules.includes(moduleSpecifier.replace('node:', ''))) { + return + } + + let resolvedPath: ResolveResult + try { + resolvedPath = resolver.sync( + path.dirname(currentFileName), + moduleSpecifier, + ) + + if (resolvedPath.error) { + // assuming ResolveError::NotFound if ResolveResult's path value is None + if (!resolvedPath.path) { + throw new ResolveError('NotFound', resolvedPath.error) + } + + throw new ResolveError(null, resolvedPath.error) + } + } catch (error) { + if (!debug) { + return + } + + if (error instanceof ResolveError) { + switch (error.errorVariant) { + case 'NotFound': { + console.error( + `Failed to resolve "${moduleSpecifier}" from "${currentFileName}": \n\n${error.stack}`, + ) + break + } + default: { + console.error(`${error.message}: \n\n${error.stack}`) + } + } + } + + const stack = error instanceof Error ? error.stack : null + console.error(`${error}: \n\n${stack}`) + return + } + + if (!resolvedPath.path) { + throw new ResolveError('NotFound', resolvedPath.error) + } + + const fileContent = readFileSync(resolvedPath.path, 'utf8') + let isBarrelFile: boolean + + /** Only cache bare module specifiers, as local files can change */ + if (isBareModuleSpecifier(moduleSpecifier)) { + /** + * The module specifier is not cached yet, so we need to analyze and + * cache it + */ + if (cache[moduleSpecifier] === undefined) { + isBarrelFile = is_barrel_file( + fileContent, + amountOfExportsToConsiderModuleAsBarrel, + ) + const moduleGraphSize = isBarrelFile + ? count_module_graph_size(resolvedPath.path, resolutionOptions) + : -1 + + cache[moduleSpecifier] = { + isBarrelFile, + moduleGraphSize, + } + + if (moduleGraphSize > maxModuleGraphSizeAllowed) { + context.report({ + node: node.source, + messageId: 'avoidImport', + data: { + amount: moduleGraphSize, + specifier: moduleSpecifier, + maxModuleGraphSizeAllowed, + }, + }) + } + } else { + /** + * It is a bare module specifier, but cached, so we can use the + * cached value + */ + + if ( + cache[moduleSpecifier].moduleGraphSize > maxModuleGraphSizeAllowed + ) { + context.report({ + node: node.source, + messageId: 'avoidImport', + data: { + amount: cache[moduleSpecifier].moduleGraphSize, + specifier: moduleSpecifier, + maxModuleGraphSizeAllowed, + }, + }) + } + } + } else { + /** + * Its not a bare module specifier, but local module, so we need to + * analyze it + */ + const isBarrelFile = is_barrel_file( + fileContent, + amountOfExportsToConsiderModuleAsBarrel, + ) + const moduleGraphSize = isBarrelFile + ? count_module_graph_size(resolvedPath.path, resolutionOptions) + : -1 + if (moduleGraphSize > maxModuleGraphSizeAllowed) { + context.report({ + node: node.source, + messageId: 'avoidImport', + data: { + amount: moduleGraphSize, + specifier: moduleSpecifier, + maxModuleGraphSizeAllowed, + }, + }) + } + } + }, + } + }, +}) diff --git a/src/rules/avoid-namespace-import.ts b/src/rules/avoid-namespace-import.ts new file mode 100644 index 00000000..6fa7626d --- /dev/null +++ b/src/rules/avoid-namespace-import.ts @@ -0,0 +1,63 @@ +import { createRule } from '../utils/index.js' + +export interface Options { + allowList: string[] +} + +export type MessageId = 'avoidNamespace' + +const defaultOptions: Options = { + allowList: [], +} + +export default createRule<[Options?], MessageId>({ + name: 'avoid-namespace-import', + meta: { + type: 'suggestion', + docs: { + description: 'Forbid namespace imports.', + category: 'Performance', + recommended: true, + }, + schema: [ + { + type: 'object', + properties: { + allowList: { + type: 'array', + description: 'List of namespace imports to allow', + default: [], + uniqueItems: true, + items: { + type: 'string', + }, + }, + }, + additionalProperties: false, + }, + ], + messages: { + avoidNamespace: + 'Avoid namespace imports, it leads to unused imports and prevents treeshaking.', + }, + }, + defaultOptions: [defaultOptions], + create: context => { + const options = context.options[0] || defaultOptions + const allowList = options.allowList + + return { + ImportNamespaceSpecifier(node) { + if ( + node.parent.importKind !== 'type' && + !allowList.includes(node.parent.source.value) + ) { + context.report({ + node, + messageId: 'avoidNamespace', + }) + } + }, + } + }, +}) diff --git a/src/rules/avoid-re-export-all.ts b/src/rules/avoid-re-export-all.ts new file mode 100644 index 00000000..a4c28d43 --- /dev/null +++ b/src/rules/avoid-re-export-all.ts @@ -0,0 +1,33 @@ +import { createRule } from '../utils/index.js' + +export type MessageId = 'avoidReExport' + +export default createRule<[], MessageId>({ + name: 'avoid-re-export-all', + meta: { + type: 'suggestion', + docs: { + description: 'Forbid re-exporting * from a module.', + category: 'Performance', + recommended: true, + }, + schema: [], + messages: { + avoidReExport: + 'Avoid re-exporting * from a module, it leads to unused imports and prevents treeshaking.', + }, + }, + defaultOptions: [], + create(context) { + return { + ExportAllDeclaration(node) { + if (node?.exportKind !== 'type') { + context.report({ + node, + messageId: 'avoidReExport', + }) + } + }, + } + }, +}) diff --git a/test/rules/avoid-barrel-files-ts.spec.ts b/test/rules/avoid-barrel-files-ts.spec.ts new file mode 100644 index 00000000..2c043172 --- /dev/null +++ b/test/rules/avoid-barrel-files-ts.spec.ts @@ -0,0 +1,61 @@ +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { createRuleTestCaseFunctions } from '../utils.js' + +import rule from 'eslint-plugin-import-x/rules/avoid-barrel-files' + +const ruleTester = new RuleTester() + +const { tValid, tInvalid } = createRuleTestCaseFunctions() + +ruleTester.run('avoid-barrel-files ts', rule, { + valid: [ + tValid({ + code: ` + type Money = string; + export type { Money }; + `, + }), + tValid({ + code: ` + type Money = { + amount: string; + currency: string; + }; + export type { Money }; + `, + }), + tValid({ + code: ` + interface Money { + amount: string; + currency: string; + }; + type Country = string; + type State = { + name: string; + }; + const newSouthWales = { + name: "New South Wales" + }; + export { newSouthWales } + export type { Money, Country, State }; + `, + }), + ], + + invalid: [ + tInvalid({ + code: ` + import { Country } from 'geo'; + type Money = string; + type State = { + name: string; + }; + interface Person { name: string; age: number; } + export type { Money, Country, Person, State }; + `, + errors: [{ messageId: 'avoidBarrel' }], + }), + ], +}) diff --git a/test/rules/avoid-barrel-files.spec.ts b/test/rules/avoid-barrel-files.spec.ts new file mode 100644 index 00000000..bd52e580 --- /dev/null +++ b/test/rules/avoid-barrel-files.spec.ts @@ -0,0 +1,109 @@ +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { createRuleTestCaseFunctions } from '../utils.js' + +import rule from 'eslint-plugin-import-x/rules/avoid-barrel-files' + +const { tValid, tInvalid } = createRuleTestCaseFunctions() + +const ruleTester = new RuleTester() + +ruleTester.run('avoid-barrel-files', rule, { + valid: [ + tValid({ + code: ` + let foo; + export { foo }; + `, + }), + tValid({ + code: ` + let foo, bar; + export { foo, bar }; + `, + }), + tValid({ + code: ` + let foo, bar, baz; + export { foo, bar, baz }; + `, + }), + tValid({ + code: ` + let foo, bar, baz, qux; + export { foo, bar, baz, qux }; + `, + }), + tValid({ + code: ` + let foo, bar, baz, qux, quux; + export { foo, bar, baz, qux }; + `, + }), + tValid({ + code: ` + export default function Foo() { + return 'bar'; + } + `, + options: [ + { + amountOfExportsToConsiderModuleAsBarrel: 0, + }, + ], + }), + tValid({ + code: ` + export default function bar() {} + `, + options: [ + { + amountOfExportsToConsiderModuleAsBarrel: 0, + }, + ], + }), + tValid({ + code: ` + export default defineFoo({}); + `, + options: [ + { + amountOfExportsToConsiderModuleAsBarrel: 0, + }, + ], + }), + ], + + invalid: [ + tInvalid({ + code: ` + import { bar, baz, qux} from 'foo'; + let foo; + export { foo, bar, baz, qux, }; + `, + errors: [{ messageId: 'avoidBarrel' }], + }), + tInvalid({ + code: ` + export * from 'foo'; + export * from 'bar'; + export * from 'baz'; + export * from 'qux'; + `, + errors: [{ messageId: 'avoidBarrel' }], + }), + tInvalid({ + code: `export { foo, bar, baz } from 'foo';`, + errors: [{ messageId: 'avoidBarrel' }], + options: [ + { + amountOfExportsToConsiderModuleAsBarrel: 2, + }, + ], + }), + tInvalid({ + code: 'export default { var1, var2, var3, var4 };', + errors: [{ messageId: 'avoidBarrel' }], + }), + ], +}) diff --git a/test/rules/avoid-namespace-import.spec.ts b/test/rules/avoid-namespace-import.spec.ts new file mode 100644 index 00000000..4b29b862 --- /dev/null +++ b/test/rules/avoid-namespace-import.spec.ts @@ -0,0 +1,41 @@ +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { createRuleTestCaseFunctions } from '../utils.js' + +import rule from 'eslint-plugin-import-x/rules/avoid-namespace-import' + +const ruleTester = new RuleTester() + +const { tValid, tInvalid } = createRuleTestCaseFunctions() + +ruleTester.run('avoid-namespace-import', rule, { + valid: [ + tValid({ code: 'import { foo } from "foo";' }), + // 'import type { foo } from "foo";', + // 'import type * as foo from "foo";' + tValid({ + code: 'import * as foo from "foo";', + options: [ + { + allowList: ['foo'], + }, + ], + }), + ], + + invalid: [ + tInvalid({ + code: 'import * as foo from "foo";', + errors: [{ messageId: 'avoidNamespace' }], + }), + tInvalid({ + code: 'import * as bar from "bar";', + errors: [{ messageId: 'avoidNamespace' }], + options: [ + { + allowList: ['foo'], + }, + ], + }), + ], +}) diff --git a/test/rules/avoid-re-export-all.spec.ts b/test/rules/avoid-re-export-all.spec.ts new file mode 100644 index 00000000..dfb7ccf6 --- /dev/null +++ b/test/rules/avoid-re-export-all.spec.ts @@ -0,0 +1,29 @@ +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { createRuleTestCaseFunctions } from '../utils.js' + +import rule from 'eslint-plugin-import-x/rules/avoid-re-export-all' + +const ruleTester = new RuleTester() + +const { tInvalid } = createRuleTestCaseFunctions() + +ruleTester.run('avoid-re-export-all', rule, { + valid: [ + // 'export type { foo } from "foo";', + // 'export type * as foo from "foo";', + 'export { foo } from "foo";', + 'export { foo as bar } from "foo";', + ], + + invalid: [ + tInvalid({ + code: 'export * from "foo";', + errors: [{ messageId: 'avoidReExport' }], + }), + tInvalid({ + code: 'export * as foo from "foo";', + errors: [{ messageId: 'avoidReExport' }], + }), + ], +}) diff --git a/yarn.lock b/yarn.lock index 46163320..594fd409 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6163,6 +6163,145 @@ __metadata: languageName: node linkType: hard +"eslint-barrel-file-utils-android-arm-eabi@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-android-arm-eabi@npm:0.0.11" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"eslint-barrel-file-utils-android-arm64@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-android-arm64@npm:0.0.11" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-darwin-arm64@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-darwin-arm64@npm:0.0.11" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-darwin-x64@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-darwin-x64@npm:0.0.11" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-freebsd-x64@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-freebsd-x64@npm:0.0.11" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.11" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.11" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.11" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.11" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"eslint-barrel-file-utils-linux-x64-musl@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-linux-x64-musl@npm:0.0.11" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.11" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.11" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.11" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"eslint-barrel-file-utils@npm:^0.0.11": + version: 0.0.11 + resolution: "eslint-barrel-file-utils@npm:0.0.11" + dependencies: + eslint-barrel-file-utils-android-arm-eabi: "npm:0.0.11" + eslint-barrel-file-utils-android-arm64: "npm:0.0.11" + eslint-barrel-file-utils-darwin-arm64: "npm:0.0.11" + eslint-barrel-file-utils-darwin-x64: "npm:0.0.11" + eslint-barrel-file-utils-freebsd-x64: "npm:0.0.11" + eslint-barrel-file-utils-linux-arm-gnueabihf: "npm:0.0.11" + eslint-barrel-file-utils-linux-arm64-gnu: "npm:0.0.11" + eslint-barrel-file-utils-linux-arm64-musl: "npm:0.0.11" + eslint-barrel-file-utils-linux-x64-gnu: "npm:0.0.11" + eslint-barrel-file-utils-linux-x64-musl: "npm:0.0.11" + eslint-barrel-file-utils-win32-arm64-msvc: "npm:0.0.11" + eslint-barrel-file-utils-win32-ia32-msvc: "npm:0.0.11" + eslint-barrel-file-utils-win32-x64-msvc: "npm:0.0.11" + dependenciesMeta: + eslint-barrel-file-utils-android-arm-eabi: + optional: true + eslint-barrel-file-utils-android-arm64: + optional: true + eslint-barrel-file-utils-darwin-arm64: + optional: true + eslint-barrel-file-utils-darwin-x64: + optional: true + eslint-barrel-file-utils-freebsd-x64: + optional: true + eslint-barrel-file-utils-linux-arm-gnueabihf: + optional: true + eslint-barrel-file-utils-linux-arm64-gnu: + optional: true + eslint-barrel-file-utils-linux-arm64-musl: + optional: true + eslint-barrel-file-utils-linux-x64-gnu: + optional: true + eslint-barrel-file-utils-linux-x64-musl: + optional: true + eslint-barrel-file-utils-win32-arm64-msvc: + optional: true + eslint-barrel-file-utils-win32-ia32-msvc: + optional: true + eslint-barrel-file-utils-win32-x64-msvc: + optional: true + checksum: 10c0/e7491b1d87f6975635e1a4e05c70ff0cbec0fe749bd03e8a48f679fd6523871e15b05b2a40445dda7ec2904eaed45185bd4d4261de9e35eca0cf5d2e17473083 + languageName: node + linkType: hard + "eslint-compat-utils@npm:^0.5.1": version: 0.5.1 resolution: "eslint-compat-utils@npm:0.5.1" @@ -6390,6 +6529,7 @@ __metadata: comment-parser: "npm:^1.4.1" debug: "npm:^4.4.0" eslint: "npm:^9.25.1" + eslint-barrel-file-utils: "npm:^0.0.11" eslint-config-prettier: "npm:^10.1.2" eslint-doc-generator: "npm:^2.1.2" eslint-import-resolver-node: "npm:^0.3.9" From 9bad699161755f71f9191d48882f2e63ab52fad9 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 7 May 2025 10:24:11 +0100 Subject: [PATCH 2/8] chore: pr fixes --- docs/rules/avoid-importing-barrel-files.md | 2 +- docs/rules/avoid-namespace-import.md | 2 +- src/rules/avoid-importing-barrel-files.ts | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/rules/avoid-importing-barrel-files.md b/docs/rules/avoid-importing-barrel-files.md index c390f0df..6e5ee5f7 100644 --- a/docs/rules/avoid-importing-barrel-files.md +++ b/docs/rules/avoid-importing-barrel-files.md @@ -68,7 +68,7 @@ const path = require('path') export default { // ... rules: { - 'barrel-files/avoid-importing-barrel-files': [ + 'import-x/avoid-importing-barrel-files': [ 2, { alias: { diff --git a/docs/rules/avoid-namespace-import.md b/docs/rules/avoid-namespace-import.md index 24c12ff2..075f1468 100644 --- a/docs/rules/avoid-namespace-import.md +++ b/docs/rules/avoid-namespace-import.md @@ -15,7 +15,7 @@ import * as foo from 'foo' This rule has the following options, with these defaults: ```js -"import-x/avoid-barrel-files": ["error", { +"import-x/avoid-namespace-import": ["error", { allowList: [] }] ``` diff --git a/src/rules/avoid-importing-barrel-files.ts b/src/rules/avoid-importing-barrel-files.ts index 269a64fb..461b6318 100644 --- a/src/rules/avoid-importing-barrel-files.ts +++ b/src/rules/avoid-importing-barrel-files.ts @@ -49,8 +49,12 @@ const cache: Record< * @param {string} specifier * @returns {boolean} */ -const isBareModuleSpecifier = (specifier: string): boolean => - !!specifier?.replace(/'/g, '')[0].match(/[@a-zA-Z]/g) +const isBareModuleSpecifier = (specifier: string): boolean => { + if (specifier && specifier.length > 0) { + return /[@a-zA-Z]/.test(specifier.replaceAll("'", '')[0]) + } + return false +} // custom error class to emulate oxc_resolver ResolveError enum. // `errorVariant` can be equal to a `ResolveError` enum variant. From fa2d7ee18cebc897a8c1a0a719a4862a8f2a0110 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 7 May 2025 10:28:44 +0100 Subject: [PATCH 3/8] chore: remove debug flag --- src/rules/avoid-importing-barrel-files.ts | 31 ++--------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/rules/avoid-importing-barrel-files.ts b/src/rules/avoid-importing-barrel-files.ts index 461b6318..b620ede0 100644 --- a/src/rules/avoid-importing-barrel-files.ts +++ b/src/rules/avoid-importing-barrel-files.ts @@ -15,7 +15,6 @@ export interface Options { allowList: string[] maxModuleGraphSizeAllowed: number amountOfExportsToConsiderModuleAsBarrel: number - debug: boolean exportConditions: string[] mainFields: string[] extensions: string[] @@ -32,7 +31,6 @@ const defaultOptions: Options = { allowList: [], maxModuleGraphSizeAllowed: 20, amountOfExportsToConsiderModuleAsBarrel: 3, - debug: false, exportConditions: ['node', 'import'], mainFields: ['module', 'browser', 'main'], extensions: ['.js', '.ts', '.tsx', '.jsx', '.json', '.node'], @@ -100,11 +98,6 @@ export default createRule<[Options?], MessageId>({ 'Amount of exports to consider a module as barrel file', default: 3, }, - debug: { - type: 'boolean', - description: 'Enabling debug logging', - default: false, - }, exportConditions: { type: 'array', description: @@ -168,7 +161,6 @@ export default createRule<[Options?], MessageId>({ create(context) { const options = context.options[0] || defaultOptions const maxModuleGraphSizeAllowed = options.maxModuleGraphSizeAllowed - const debug = options.debug const amountOfExportsToConsiderModuleAsBarrel = options.amountOfExportsToConsiderModuleAsBarrel const exportConditions = options.exportConditions @@ -225,27 +217,8 @@ export default createRule<[Options?], MessageId>({ throw new ResolveError(null, resolvedPath.error) } - } catch (error) { - if (!debug) { - return - } - - if (error instanceof ResolveError) { - switch (error.errorVariant) { - case 'NotFound': { - console.error( - `Failed to resolve "${moduleSpecifier}" from "${currentFileName}": \n\n${error.stack}`, - ) - break - } - default: { - console.error(`${error.message}: \n\n${error.stack}`) - } - } - } - - const stack = error instanceof Error ? error.stack : null - console.error(`${error}: \n\n${stack}`) + } catch { + // do nothing since we couldn't resolve it return } From 8322621ac160198db35f2f77d88c1d8890a1c6b4 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 7 May 2025 10:30:09 +0100 Subject: [PATCH 4/8] chore: leftover debug flags --- docs/rules/avoid-importing-barrel-files.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/rules/avoid-importing-barrel-files.md b/docs/rules/avoid-importing-barrel-files.md index 6e5ee5f7..adc537ba 100644 --- a/docs/rules/avoid-importing-barrel-files.md +++ b/docs/rules/avoid-importing-barrel-files.md @@ -20,7 +20,6 @@ This rule has the following options, with these defaults: allowList: [], maxModuleGraphSizeAllowed: 20, amountOfExportsToConsiderModuleAsBarrel: 3, - debug: false, exportConditions: ["node", "import"], mainFields: ["module", "browser", "main"], extensions: [".js", ".ts", ".tsx", ".jsx", ".json", ".node"], @@ -69,7 +68,7 @@ export default { // ... rules: { 'import-x/avoid-importing-barrel-files': [ - 2, + 'error', { alias: { // "@/foo/bar.js" => "./src/foo/bar.js" From eeb133dfc413b3dd1e0ea1333f86c103a33ec62b Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 7 May 2025 10:33:02 +0100 Subject: [PATCH 5/8] docs: update example to flat config --- docs/rules/avoid-importing-barrel-files.md | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/rules/avoid-importing-barrel-files.md b/docs/rules/avoid-importing-barrel-files.md index adc537ba..aa3df3f1 100644 --- a/docs/rules/avoid-importing-barrel-files.md +++ b/docs/rules/avoid-importing-barrel-files.md @@ -61,21 +61,27 @@ This can be useful when resolving project references in TypeScript. 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 -// .eslintrc.cjs -const path = require('path') - -export default { - // ... - rules: { - 'import-x/avoid-importing-barrel-files': [ - 'error', - { - alias: { - // "@/foo/bar.js" => "./src/foo/bar.js" - '@': [path.resolve('.', 'src')], +// 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')], + }, }, - }, - ], + ], + }, }, -} +]) ``` From 8b98c331ae81424301d24a6ab8bb2cc6eaccce46 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:30:22 +0100 Subject: [PATCH 6/8] chore: re-use default options --- src/rules/avoid-importing-barrel-files.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rules/avoid-importing-barrel-files.ts b/src/rules/avoid-importing-barrel-files.ts index b620ede0..2781bc41 100644 --- a/src/rules/avoid-importing-barrel-files.ts +++ b/src/rules/avoid-importing-barrel-files.ts @@ -81,7 +81,7 @@ export default createRule<[Options?], MessageId>({ allowList: { type: 'array', description: 'List of modules from which to allow barrel files', - default: [], + default: defaultOptions.allowList, uniqueItems: true, items: { type: 'string', @@ -90,19 +90,19 @@ export default createRule<[Options?], MessageId>({ maxModuleGraphSizeAllowed: { type: 'number', description: 'Maximum allowed module graph size', - default: 20, + default: defaultOptions.maxModuleGraphSizeAllowed, }, amountOfExportsToConsiderModuleAsBarrel: { type: 'number', description: 'Amount of exports to consider a module as barrel file', - default: 3, + default: defaultOptions.amountOfExportsToConsiderModuleAsBarrel, }, exportConditions: { type: 'array', description: 'Export conditions to use to resolve bare module specifiers', - default: [], + default: defaultOptions.exportConditions, uniqueItems: true, items: { type: 'string', @@ -111,7 +111,7 @@ export default createRule<[Options?], MessageId>({ mainFields: { type: 'array', description: 'Main fields to use to resolve modules', - default: [], + default: defaultOptions.mainFields, uniqueItems: true, items: { type: 'string', @@ -120,7 +120,7 @@ export default createRule<[Options?], MessageId>({ extensions: { type: 'array', description: 'Extensions to use to resolve modules', - default: [], + default: defaultOptions.extensions, uniqueItems: true, items: { type: 'string', @@ -157,7 +157,7 @@ export default createRule<[Options?], MessageId>({ 'The imported module "{{specifier}}" is a barrel file, which leads to importing a module graph of {{amount}} modules, which exceeds the maximum allowed size of {{maxModuleGraphSizeAllowed}} modules', }, }, - defaultOptions: [defaultOptions], + defaultOptions: [], create(context) { const options = context.options[0] || defaultOptions const maxModuleGraphSizeAllowed = options.maxModuleGraphSizeAllowed From d6425c171423234180dfe4a7cfd487de4bc17224 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:49:00 +0100 Subject: [PATCH 7/8] chore: upgrade barrel utils --- package.json | 2 +- yarn.lock | 116 +++++++++++++++++++++++++-------------------------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index ae7feb68..8c39d76b 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", - "eslint-barrel-file-utils": "^0.0.11", + "eslint-barrel-file-utils": "^0.0.15", "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", diff --git a/yarn.lock b/yarn.lock index ba9cd7a4..b3f6ac92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5756,114 +5756,114 @@ __metadata: languageName: node linkType: hard -"eslint-barrel-file-utils-android-arm-eabi@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-android-arm-eabi@npm:0.0.11" +"eslint-barrel-file-utils-android-arm-eabi@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-android-arm-eabi@npm:0.0.15" conditions: os=android & cpu=arm languageName: node linkType: hard -"eslint-barrel-file-utils-android-arm64@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-android-arm64@npm:0.0.11" +"eslint-barrel-file-utils-android-arm64@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-android-arm64@npm:0.0.15" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"eslint-barrel-file-utils-darwin-arm64@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-darwin-arm64@npm:0.0.11" +"eslint-barrel-file-utils-darwin-arm64@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-darwin-arm64@npm:0.0.15" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"eslint-barrel-file-utils-darwin-x64@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-darwin-x64@npm:0.0.11" +"eslint-barrel-file-utils-darwin-x64@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-darwin-x64@npm:0.0.15" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"eslint-barrel-file-utils-freebsd-x64@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-freebsd-x64@npm:0.0.11" +"eslint-barrel-file-utils-freebsd-x64@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-freebsd-x64@npm:0.0.15" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.11" +"eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-linux-arm-gnueabihf@npm:0.0.15" conditions: os=linux & cpu=arm languageName: node linkType: hard -"eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.11" +"eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-linux-arm64-gnu@npm:0.0.15" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.11" +"eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-linux-arm64-musl@npm:0.0.15" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.11" +"eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-linux-x64-gnu@npm:0.0.15" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"eslint-barrel-file-utils-linux-x64-musl@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-linux-x64-musl@npm:0.0.11" +"eslint-barrel-file-utils-linux-x64-musl@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-linux-x64-musl@npm:0.0.15" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.11" +"eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-win32-arm64-msvc@npm:0.0.15" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.11" +"eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-win32-ia32-msvc@npm:0.0.15" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.11" +"eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils-win32-x64-msvc@npm:0.0.15" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"eslint-barrel-file-utils@npm:^0.0.11": - version: 0.0.11 - resolution: "eslint-barrel-file-utils@npm:0.0.11" - dependencies: - eslint-barrel-file-utils-android-arm-eabi: "npm:0.0.11" - eslint-barrel-file-utils-android-arm64: "npm:0.0.11" - eslint-barrel-file-utils-darwin-arm64: "npm:0.0.11" - eslint-barrel-file-utils-darwin-x64: "npm:0.0.11" - eslint-barrel-file-utils-freebsd-x64: "npm:0.0.11" - eslint-barrel-file-utils-linux-arm-gnueabihf: "npm:0.0.11" - eslint-barrel-file-utils-linux-arm64-gnu: "npm:0.0.11" - eslint-barrel-file-utils-linux-arm64-musl: "npm:0.0.11" - eslint-barrel-file-utils-linux-x64-gnu: "npm:0.0.11" - eslint-barrel-file-utils-linux-x64-musl: "npm:0.0.11" - eslint-barrel-file-utils-win32-arm64-msvc: "npm:0.0.11" - eslint-barrel-file-utils-win32-ia32-msvc: "npm:0.0.11" - eslint-barrel-file-utils-win32-x64-msvc: "npm:0.0.11" +"eslint-barrel-file-utils@npm:^0.0.15": + version: 0.0.15 + resolution: "eslint-barrel-file-utils@npm:0.0.15" + dependencies: + eslint-barrel-file-utils-android-arm-eabi: "npm:0.0.15" + eslint-barrel-file-utils-android-arm64: "npm:0.0.15" + eslint-barrel-file-utils-darwin-arm64: "npm:0.0.15" + eslint-barrel-file-utils-darwin-x64: "npm:0.0.15" + eslint-barrel-file-utils-freebsd-x64: "npm:0.0.15" + eslint-barrel-file-utils-linux-arm-gnueabihf: "npm:0.0.15" + eslint-barrel-file-utils-linux-arm64-gnu: "npm:0.0.15" + eslint-barrel-file-utils-linux-arm64-musl: "npm:0.0.15" + eslint-barrel-file-utils-linux-x64-gnu: "npm:0.0.15" + eslint-barrel-file-utils-linux-x64-musl: "npm:0.0.15" + eslint-barrel-file-utils-win32-arm64-msvc: "npm:0.0.15" + eslint-barrel-file-utils-win32-ia32-msvc: "npm:0.0.15" + eslint-barrel-file-utils-win32-x64-msvc: "npm:0.0.15" dependenciesMeta: eslint-barrel-file-utils-android-arm-eabi: optional: true @@ -5891,7 +5891,7 @@ __metadata: optional: true eslint-barrel-file-utils-win32-x64-msvc: optional: true - checksum: 10c0/e7491b1d87f6975635e1a4e05c70ff0cbec0fe749bd03e8a48f679fd6523871e15b05b2a40445dda7ec2904eaed45185bd4d4261de9e35eca0cf5d2e17473083 + checksum: 10c0/b74cbb95fc74b47a85ab5545ee3f83ddafd043510739c179123166f74e025dbd80930c1867a2a813061d452004f4cb9b5b364f1c02bedb5783d52dde5ba00223 languageName: node linkType: hard @@ -6127,7 +6127,7 @@ __metadata: comment-parser: "npm:^1.4.1" debug: "npm:^4.4.1" eslint: "npm:^9.29.0" - eslint-barrel-file-utils: "npm:^0.0.11" + eslint-barrel-file-utils: "npm:^0.0.15" eslint-config-prettier: "npm:^10.1.5" eslint-doc-generator: "npm:^2.2.2" eslint-import-context: "npm:^0.1.9" From ce9d6dd58b1ec1f627c2335f23e16af7f47fecfb Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:01:13 +0100 Subject: [PATCH 8/8] docs: update docs for barrel rules --- README.md | 12 ++++++++++++ docs/rules/avoid-barrel-files.md | 2 ++ docs/rules/avoid-importing-barrel-files.md | 2 ++ docs/rules/avoid-namespace-import.md | 2 ++ docs/rules/avoid-re-export-all.md | 2 ++ 5 files changed, 20 insertions(+) diff --git a/README.md b/README.md index a0b8b586..3385c48c 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,10 @@ settings: 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\ ❌ Deprecated. +| Name | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | ❌ | +| :------------------------------------------------------------------------- | :----------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | +| [avoid-importing-barrel-files](docs/rules/avoid-importing-barrel-files.md) | Forbid importing barrel files. | | | | | | | + ### Helpful warnings | Name                       | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | ❌ | @@ -277,6 +281,14 @@ settings: | [no-nodejs-modules](docs/rules/no-nodejs-modules.md) | Forbid Node.js builtin modules. | | | | | | | | [unambiguous](docs/rules/unambiguous.md) | Forbid potentially ambiguous parse goal (`script` vs. `module`). | | | | | | | +### Performance + +| Name | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | ❌ | +| :------------------------------------------------------------- | :------------------------------------ | :-- | :-- | :-- | :-- | :-- | :-- | +| [avoid-barrel-files](docs/rules/avoid-barrel-files.md) | Forbid authoring of barrel files. | | | | | | | +| [avoid-namespace-import](docs/rules/avoid-namespace-import.md) | Forbid namespace imports. | | | | | | | +| [avoid-re-export-all](docs/rules/avoid-re-export-all.md) | Forbid re-exporting \* from a module. | | | | | | | + ### Static analysis | Name                       | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | ❌ | diff --git a/docs/rules/avoid-barrel-files.md b/docs/rules/avoid-barrel-files.md index 76e934fb..1acf2a41 100644 --- a/docs/rules/avoid-barrel-files.md +++ b/docs/rules/avoid-barrel-files.md @@ -1,5 +1,7 @@ # import-x/avoid-barrel-files + + This rule disallows _authoring_ barrel files in your project. ## Rule Details diff --git a/docs/rules/avoid-importing-barrel-files.md b/docs/rules/avoid-importing-barrel-files.md index aa3df3f1..84b35aa1 100644 --- a/docs/rules/avoid-importing-barrel-files.md +++ b/docs/rules/avoid-importing-barrel-files.md @@ -1,5 +1,7 @@ # 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 diff --git a/docs/rules/avoid-namespace-import.md b/docs/rules/avoid-namespace-import.md index 075f1468..82979a5c 100644 --- a/docs/rules/avoid-namespace-import.md +++ b/docs/rules/avoid-namespace-import.md @@ -1,5 +1,7 @@ # 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 diff --git a/docs/rules/avoid-re-export-all.md b/docs/rules/avoid-re-export-all.md index 44d3af54..dbc4cddf 100644 --- a/docs/rules/avoid-re-export-all.md +++ b/docs/rules/avoid-re-export-all.md @@ -1,5 +1,7 @@ # 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