diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9e9c5c2267a10..816c908cf88f4 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -91,6 +91,7 @@ mod eslint { pub mod no_useless_catch; pub mod no_useless_escape; pub mod no_var; + pub mod no_void; pub mod require_yield; pub mod use_isnan; pub mod valid_typeof; @@ -379,6 +380,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_useless_catch, eslint::no_useless_escape, eslint::no_var, + eslint::no_void, eslint::require_yield, eslint::use_isnan, eslint::valid_typeof, diff --git a/crates/oxc_linter/src/rules/eslint/no_void.rs b/crates/oxc_linter/src/rules/eslint/no_void.rs new file mode 100644 index 0000000000000..77702aa2fe552 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_void.rs @@ -0,0 +1,97 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; + +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::operator::UnaryOperator; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-void): Disallow `void` operators")] +#[diagnostic(severity(warning), help("Expected 'undefined' and instead saw 'void'."))] +struct NoVoidDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoVoid { + pub allow_as_statement: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow `void` operators. + /// + /// ### Example + /// + /// ```javascript + /// // error + /// void 0; + /// var foo = void 0; + /// + /// // success + /// "var foo = bar()", + /// "foo.void()", + /// "foo.void = bar", + /// ``` + NoVoid, + restriction, +); + +impl Rule for NoVoid { + fn from_configuration(value: serde_json::Value) -> Self { + let allow_as_statement = value + .get(0) + .and_then(|config| config.get("allowAsStatement")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Self { allow_as_statement } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::UnaryExpression(unary_expr) = node.kind() else { + return; + }; + + if let Some(kind) = ctx.nodes().parent_kind(node.id()) { + if self.allow_as_statement && matches!(kind, AstKind::ExpressionStatement(_)) { + return; + } + }; + + if unary_expr.operator == UnaryOperator::Void { + ctx.diagnostic(NoVoidDiagnostic(Span { + start: unary_expr.span.start, + end: unary_expr.span.start + 4, + })); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var foo = bar()", None), + ("foo.void()", None), + ("foo.void = bar", None), + ("delete foo;", None), + ("void 0", Some(serde_json::json!([{ "allowAsStatement": true }]))), + ("void(0)", Some(serde_json::json!([{ "allowAsStatement": true }]))), + ]; + + let fail = vec![ + ("void 0", None), + ("void 0", Some(serde_json::json!([{}]))), + ("void 0", Some(serde_json::json!([{ "allowAsStatement": false }]))), + ("void(0)", None), + ("var foo = void 0", None), + ("var foo = void 0", Some(serde_json::json!([{ "allowAsStatement": true }]))), + ]; + + Tester::new(NoVoid::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_void.snap b/crates/oxc_linter/src/snapshots/no_void.snap new file mode 100644 index 0000000000000..b22680b2fe177 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_void.snap @@ -0,0 +1,48 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 150 +expression: no_void +--- + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ void 0 + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ void 0 + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ void 0 + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ void(0) + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ var foo = void 0 + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + + ⚠ eslint(no-void): Disallow `void` operators + ╭─[no_void.tsx:1:1] + 1 │ var foo = void 0 + · ──── + ╰──── + help: Expected 'undefined' and instead saw 'void'. + +