feat(linter): no barrel file. (#3030)

closes #3004 

I've based it on [this plugin](https://github.com/thepassle/eslint-plugin-barrel-files/blob/main/lib/rules/avoid-barrel-files.js) instead of [biome](4130ff0e4c/crates/biome_js_analyze/src/lint/performance/no_barrel_file.rs (L70)) Since the original plugin is less likely to detect a false positive.

I didn't understand your statement [here](https://github.com/oxc-project/oxc/issues/3004#issue-2245574895), Where you've mentioned:

> In oxlint, we can do better when --import-plugin is enabled, by displaying the total number of dependencies pulled into a given file by walking

I would appreciate it if you expand upon it.

------

#### Edit:

I've added it under `eslint-plugin-import` even though it is not; I wasn't sure If I should create a whole new category for this single rule, It is a different story if we would like to adopt the other rules in [here](https://github.com/thepassle/eslint-plugin-barrel-files/tree/main/lib/rules).

Also, check my diagnosis messages; It is my first contribution to the linters so I'm just not familiar enough with the conventions of messages, rules, etc.
This commit is contained in:
Ali Rezvani 2024-04-22 05:48:33 +03:30 committed by GitHub
parent e6d11c6190
commit 5cf55c212e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 159 additions and 0 deletions

View file

@ -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,

View file

@ -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: <https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/>"))]
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;
/// <https://github.com/thepassle/eslint-plugin-barrel-files/blob/main/docs/rules/avoid-barrel-files.md>
#[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();
}

View file

@ -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: <https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/>
⚠ 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: <https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/>
⚠ 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: <https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/>
⚠ 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: <https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/>