diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 644db2d1d5ceb..a401fd41b80e1 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -136,6 +136,7 @@ mod typescript { pub mod no_this_alias; pub mod no_unnecessary_type_constraint; pub mod no_unsafe_declaration_merging; + pub mod no_useless_empty_export; pub mod no_var_requires; pub mod prefer_as_const; pub mod prefer_enum_initializers; @@ -522,6 +523,7 @@ oxc_macros::declare_all_lint_rules! { typescript::no_this_alias, typescript::no_unnecessary_type_constraint, typescript::no_unsafe_declaration_merging, + typescript::no_useless_empty_export, typescript::no_var_requires, typescript::prefer_as_const, typescript::prefer_for_of, diff --git a/crates/oxc_linter/src/rules/typescript/no_useless_empty_export.rs b/crates/oxc_linter/src/rules/typescript/no_useless_empty_export.rs new file mode 100644 index 0000000000000..9c00edd68ef8c --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_useless_empty_export.rs @@ -0,0 +1,129 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_useless_empty_export_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn( + "typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file", + ) + .with_help("Empty export does nothing and can be removed.") + .with_labels([span0.into()]) +} + +#[derive(Debug, Default, Clone)] +pub struct NoUselessEmptyExport; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow empty exports that don't change anything in a module file. + /// + /// ### Example + /// + /// ### Bad + /// ```javascript + /// export const value = 'Hello, world!'; + /// export {}; + /// ``` + /// + /// ### Good + /// ```javascript + /// export const value = 'Hello, world!'; + /// ``` + /// + NoUselessEmptyExport, + correctness +); + +impl Rule for NoUselessEmptyExport { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::ExportNamedDeclaration(decl) = node.kind() else { return }; + if decl.declaration.is_some() || !decl.specifiers.is_empty() { + return; + } + let module_record = ctx.semantic().module_record(); + if module_record.exported_bindings.is_empty() + && module_record.local_export_entries.is_empty() + && module_record.indirect_export_entries.is_empty() + && module_record.star_export_entries.is_empty() + && module_record.export_default.is_none() + { + return; + } + ctx.diagnostic_with_fix(no_useless_empty_export_diagnostic(decl.span), |fixer| { + fixer.delete(&decl.span) + }); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "declare module '_'", + "import {} from '_';", + "import * as _ from '_';", + "export = {};", + "export = 3;", + "export const _ = {};", + " + const _ = {}; + export default _; + ", + " + export * from '_'; + export = {}; + ", + "export {};", + ]; + + let fail = vec![ + " + export const _ = {}; + export {}; + ", + " + export * from '_'; + export {}; + ", + " + export {}; + export * from '_'; + ", + " + const _ = {}; + export default _; + export {}; + ", + " + export {}; + const _ = {}; + export default _; + ", + " + const _ = {}; + export { _ }; + export {}; + ", + // " + // import _ = require('_'); + // export {}; + // ", + ]; + + let fix = vec![ + ("export const _ = {};export {};", "export const _ = {};"), + ("export * from '_';export {};", "export * from '_';"), + ("export {};export * from '_';", "export * from '_';"), + ("const _ = {};export default _;export {};", "const _ = {};export default _;"), + ("export {};const _ = {};export default _;", "const _ = {};export default _;"), + ("const _ = {};export { _ };export {};", "const _ = {};export { _ };"), + // ("import _ = require('_');export {};", "import _ = require('_');"), + ]; + + Tester::new(NoUselessEmptyExport::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_useless_empty_export.snap b/crates/oxc_linter/src/snapshots/no_useless_empty_export.snap new file mode 100644 index 0000000000000..d7d9d929ca2e2 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_useless_empty_export.snap @@ -0,0 +1,57 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_useless_empty_export +--- + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:3:13] + 2 │ export const _ = {}; + 3 │ export {}; + · ────────── + 4 │ + ╰──── + help: Empty export does nothing and can be removed. + + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:3:13] + 2 │ export * from '_'; + 3 │ export {}; + · ────────── + 4 │ + ╰──── + help: Empty export does nothing and can be removed. + + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:2:13] + 1 │ + 2 │ export {}; + · ────────── + 3 │ export * from '_'; + ╰──── + help: Empty export does nothing and can be removed. + + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:4:13] + 3 │ export default _; + 4 │ export {}; + · ────────── + 5 │ + ╰──── + help: Empty export does nothing and can be removed. + + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:2:13] + 1 │ + 2 │ export {}; + · ────────── + 3 │ const _ = {}; + ╰──── + help: Empty export does nothing and can be removed. + + ⚠ typescript-eslint(no-useless-empty-export): Disallow empty exports that don't change anything in a module file + ╭─[no_useless_empty_export.tsx:4:13] + 3 │ export { _ }; + 4 │ export {}; + · ────────── + 5 │ + ╰──── + help: Empty export does nothing and can be removed.