From 4733b522e7427940300b32f4ebc219a3b2805d15 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Fri, 9 May 2025 16:40:55 +0000 Subject: [PATCH] feat(linter/no-extraneous-class): add conditional fixer (#10798) ## What This PR Does Adds a `suggestion`-level auto-fixer to remove empty classes. Empty classes with decorators are not removed. --- .../rules/typescript/no_extraneous_class.rs | 47 +++++++++++++++---- .../typescript_no_extraneous_class.snap | 3 ++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/oxc_linter/src/rules/typescript/no_extraneous_class.rs b/crates/oxc_linter/src/rules/typescript/no_extraneous_class.rs index c2d8107671e04..a58348718e166 100644 --- a/crates/oxc_linter/src/rules/typescript/no_extraneous_class.rs +++ b/crates/oxc_linter/src/rules/typescript/no_extraneous_class.rs @@ -69,18 +69,17 @@ declare_oxc_lint!( /// ``` NoExtraneousClass, typescript, - suspicious + suspicious, + dangerous_suggestion ); fn empty_class_diagnostic(span: Span, has_decorators: bool) -> OxcDiagnostic { - let diagnostic = OxcDiagnostic::warn("Unexpected empty class.").with_label(span); - if has_decorators { - diagnostic.with_help( - r#"Set "allowWithDecorator": true in your config to allow empty decorated classes"#, - ) + let help = if has_decorators { + r#"Set "allowWithDecorator": true in your config to allow empty decorated classes"# } else { - diagnostic - } + "Delete this class" + }; + OxcDiagnostic::warn("Unexpected empty class.").with_label(span).with_help(help) } fn only_static_no_extraneous_class_diagnostic(span: Span) -> OxcDiagnostic { @@ -147,7 +146,22 @@ impl Rule for NoExtraneousClass { unsafe { std::hint::assert_unchecked(start <= u32::MAX as usize) }; span = span.shrink_left(start as u32); } - ctx.diagnostic(empty_class_diagnostic(span, !class.decorators.is_empty())); + let has_decorators = !class.decorators.is_empty(); + ctx.diagnostic_with_suggestion( + empty_class_diagnostic(span, has_decorators), + |fixer| { + if has_decorators { + return fixer.noop(); + } + if let Some(AstKind::ExportNamedDeclaration(decl)) = + ctx.nodes().parent_kind(node.id()) + { + fixer.delete(decl) + } else { + fixer.delete(class) + } + }, + ); } } [ClassElement::MethodDefinition(constructor)] if constructor.kind.is_constructor() => { @@ -308,5 +322,18 @@ fn test() { ("abstract class Foo { constructor() {} }", None), ]; - Tester::new(NoExtraneousClass::NAME, NoExtraneousClass::PLUGIN, pass, fail).test_and_snapshot(); + let fix = vec![ + ("class Foo {}", "", None, FixKind::DangerousSuggestion), + ("export class Foo {}", "", None, FixKind::DangerousSuggestion), + ( + "@foo class Foo {}", + "@foo class Foo {}", + Some(json!([{ "allowWithDecorator": false }])), + FixKind::DangerousSuggestion, + ), + ]; + + Tester::new(NoExtraneousClass::NAME, NoExtraneousClass::PLUGIN, pass, fail) + .expect_fix(fix) + .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/typescript_no_extraneous_class.snap b/crates/oxc_linter/src/snapshots/typescript_no_extraneous_class.snap index ac5dd3760dc43..32f8c42c1b71d 100644 --- a/crates/oxc_linter/src/snapshots/typescript_no_extraneous_class.snap +++ b/crates/oxc_linter/src/snapshots/typescript_no_extraneous_class.snap @@ -6,6 +6,7 @@ source: crates/oxc_linter/src/tester.rs 1 │ class Foo {} · ──────────── ╰──── + help: Delete this class ⚠ typescript-eslint(no-extraneous-class): Unexpected class with only static properties. ╭─[no_extraneous_class.tsx:5:14] @@ -39,6 +40,7 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────── 9 │ } ╰──── + help: Delete this class ⚠ typescript-eslint(no-extraneous-class): Unexpected class with only static properties. ╭─[no_extraneous_class.tsx:1:16] @@ -79,6 +81,7 @@ source: crates/oxc_linter/src/tester.rs 1 │ abstract class Foo {} · ───────────────────── ╰──── + help: Delete this class ⚠ typescript-eslint(no-extraneous-class): Unexpected class with only static properties. ╭─[no_extraneous_class.tsx:1:16]