diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index a293dc7b4..041969d1e 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -333,6 +333,7 @@ mod oxc { pub mod erasing_op; pub mod misrefactored_assign_op; pub mod no_accumulating_spread; + pub mod no_barrel_file; pub mod only_used_in_recursion; } @@ -671,6 +672,7 @@ oxc_macros::declare_all_lint_rules! { oxc::erasing_op, oxc::misrefactored_assign_op, oxc::no_accumulating_spread, + oxc::no_barrel_file, oxc::only_used_in_recursion, nextjs::google_font_display, nextjs::google_font_preconnect, diff --git a/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs new file mode 100644 index 000000000..4e248965f --- /dev/null +++ b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs @@ -0,0 +1,114 @@ +use oxc_ast::{ast::Statement, AstKind}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::module_graph_visitor::{ModuleGraphVisitorBuilder, VisitFoldWhile}; + +use crate::{context::LintContext, rule::Rule}; + +#[derive(Debug, Error, Diagnostic)] +#[error( + "oxc(no-barrel-file): \ + Avoid barrel files, they slow down performance, \ + and cause large module graphs with modules that go unused.\n\ + Loading this barrel file results in importing {1:?} modules." +)] +#[diagnostic(severity(warning), help("For more information visit this link: "))] +struct NoBarrelFileDiagnostic(#[label] pub Span, pub u32); + +/// Minimum amount of exports to consider module as barrelfile +const AMOUNT_OF_EXPORTS_TO_CONSIDER_MODULE_AS_BARREL: u8 = 3; + +/// +#[derive(Debug, Default, Clone)] +pub struct NoBarrelFile; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow the use of barrel files. + /// + /// ### Example + /// + /// Invalid: + /// ```javascript + /// export { foo } from 'foo'; + /// export { bar } from 'bar'; + /// export { baz } from 'baz'; + /// export { qux } from 'qux'; + /// ``` + /// Valid: + /// ```javascript + /// export type { foo } from './foo.js'; + /// ``` + NoBarrelFile, + nursery +); + +impl Rule for NoBarrelFile { + fn run_once(&self, ctx: &LintContext<'_>) { + let semantic = ctx.semantic(); + let module_record = semantic.module_record(); + let root = semantic.nodes().root_node(); + + let AstKind::Program(program) = root.kind() else { unreachable!() }; + + let declarations = program.body.iter().fold(0, |acc, node| match node { + Statement::Declaration(_) => acc + 1, + _ => acc, + }); + let exports = + module_record.star_export_entries.len() + module_record.indirect_export_entries.len(); + + if exports > declarations + && exports > AMOUNT_OF_EXPORTS_TO_CONSIDER_MODULE_AS_BARREL as usize + { + let loaded_modules_count = ModuleGraphVisitorBuilder::default() + .visit_fold(0, module_record, |acc, _, _| VisitFoldWhile::Next(acc + 1)) + .result; + ctx.diagnostic(NoBarrelFileDiagnostic(program.span, loaded_modules_count)); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"export type * from "foo";"#, + r#"export type { foo } from "foo";"#, + r#"export type * from "foo"; + export type { bar } from "bar";"#, + r#"import { foo, bar, baz } from "../feature"; + export { foo }; + export { bar };"#, + ]; + + let fail = vec![ + r#"export * from "./deep/a.js"; + export * from "./deep/b.js"; + export * from "./deep/c.js"; + export * from "./deep/d.js";"#, + r#"export { foo } from "foo"; + export { bar } from "bar"; + export { baz } from "baz"; + export { qux } from "qux";"#, + r#"export { default as module1 } from "./module1"; + export { default as module2 } from "./module2"; + export { default as module3 } from "./module3"; + export { default as module4 } from "./module4";"#, + r#"export { foo, type Foo } from "foo"; + export { bar, type Bar } from "bar"; + export { baz, type Baz } from "baz"; + export { qux, type Qux } from "qux";"#, + ]; + + Tester::new(NoBarrelFile::NAME, pass, fail) + .change_rule_path("index.ts") + .with_import_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_barrel_file.snap b/crates/oxc_linter/src/snapshots/no_barrel_file.snap new file mode 100644 index 000000000..38fb1c178 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_barrel_file.snap @@ -0,0 +1,43 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_barrel_file +--- + ⚠ oxc(no-barrel-file): Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused. + │ Loading this barrel file results in importing 4 modules. + ╭─[index.ts:1:1] + 1 │ ╭─▶ export * from "./deep/a.js"; + 2 │ │ export * from "./deep/b.js"; + 3 │ │ export * from "./deep/c.js"; + 4 │ ╰─▶ export * from "./deep/d.js"; + ╰──── + help: For more information visit this link: + + ⚠ oxc(no-barrel-file): Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused. + │ Loading this barrel file results in importing 0 modules. + ╭─[index.ts:1:1] + 1 │ ╭─▶ export { foo } from "foo"; + 2 │ │ export { bar } from "bar"; + 3 │ │ export { baz } from "baz"; + 4 │ ╰─▶ export { qux } from "qux"; + ╰──── + help: For more information visit this link: + + ⚠ oxc(no-barrel-file): Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused. + │ Loading this barrel file results in importing 0 modules. + ╭─[index.ts:1:1] + 1 │ ╭─▶ export { default as module1 } from "./module1"; + 2 │ │ export { default as module2 } from "./module2"; + 3 │ │ export { default as module3 } from "./module3"; + 4 │ ╰─▶ export { default as module4 } from "./module4"; + ╰──── + help: For more information visit this link: + + ⚠ oxc(no-barrel-file): Avoid barrel files, they slow down performance, and cause large module graphs with modules that go unused. + │ Loading this barrel file results in importing 0 modules. + ╭─[index.ts:1:1] + 1 │ ╭─▶ export { foo, type Foo } from "foo"; + 2 │ │ export { bar, type Bar } from "bar"; + 3 │ │ export { baz, type Baz } from "baz"; + 4 │ ╰─▶ export { qux, type Qux } from "qux"; + ╰──── + help: For more information visit this link: