diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9eac14188..7f124fc93 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -1,10 +1,12 @@ mod no_array_constructor; mod no_debugger; mod no_empty; +mod no_empty_pattern; pub use no_array_constructor::NoArrayConstructor; pub use no_debugger::NoDebugger; pub use no_empty::NoEmpty; +pub use no_empty_pattern::NoEmptyPattern; use crate::{context::LintContext, rule::Rule, rule::RuleMeta, AstNode}; @@ -13,6 +15,7 @@ lazy_static::lazy_static! { RuleEnum::NoDebugger(NoDebugger::default()), RuleEnum::NoEmpty(NoEmpty::default()), RuleEnum::NoArrayConstructor(NoArrayConstructor::default()), + RuleEnum::NoEmptyPattern(NoEmptyPattern::default()), ]; } @@ -22,6 +25,7 @@ pub enum RuleEnum { NoDebugger(NoDebugger), NoEmpty(NoEmpty), NoArrayConstructor(NoArrayConstructor), + NoEmptyPattern(NoEmptyPattern), } impl RuleEnum { @@ -30,6 +34,7 @@ impl RuleEnum { Self::NoDebugger(_) => NoDebugger::NAME, Self::NoEmpty(_) => NoEmpty::NAME, Self::NoArrayConstructor(_) => NoArrayConstructor::NAME, + Self::NoEmptyPattern(_) => NoEmptyPattern::NAME, } } @@ -44,6 +49,9 @@ impl RuleEnum { Self::NoArrayConstructor(_) => Self::NoArrayConstructor( maybe_value.map(NoArrayConstructor::from_configuration).unwrap_or_default(), ), + Self::NoEmptyPattern(_) => Self::NoEmptyPattern( + maybe_value.map(NoEmptyPattern::from_configuration).unwrap_or_default(), + ), } } @@ -52,6 +60,7 @@ impl RuleEnum { Self::NoDebugger(rule) => rule.run(node, ctx), Self::NoEmpty(rule) => rule.run(node, ctx), Self::NoArrayConstructor(rule) => rule.run(node, ctx), + Self::NoEmptyPattern(rule) => rule.run(node, ctx), } } } diff --git a/crates/oxc_linter/src/rules/no_empty_pattern.rs b/crates/oxc_linter/src/rules/no_empty_pattern.rs new file mode 100644 index 000000000..fb61fbe2a --- /dev/null +++ b/crates/oxc_linter/src/rules/no_empty_pattern.rs @@ -0,0 +1,119 @@ +use oxc_ast::{AstKind, Span}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-empty-pattern): Disallow empty destructuring patterns")] +#[diagnostic()] +struct NoEmptyPatternDiagnostic(&'static str, #[label("Empty {0} binding pattern")] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoEmptyPattern; + +declare_oxc_lint!( + /// ### What it does + /// Disallow empty destructuring patterns + /// + /// ### Why is this bad? + /// When using destructuring, it’s possible to create a pattern that has no effect. + /// This happens when empty curly braces are used to the right of + /// an embedded object destructuring pattern, such as: + /// + /// ```JavaScript + /// // doesn't create any variables + /// var {a: {}} = foo; + /// ``` + /// In this code, no new variables are created because a is just a location helper + /// while the `{}` is expected to contain the variables to create, such as: + /// + /// ```JavaScript + /// // creates variable b + /// var {a: { b }} = foo; + /// ``` + /// + /// In many cases, the empty object pattern is a mistake + /// where the author intended to use a default value instead, such as: + /// + /// ```JavaScript + /// // creates variable a + /// var {a = {}} = foo; + /// ``` + /// + /// The difference between these two patterns is subtle, + /// especially because the problematic empty pattern looks just like an object literal. + /// + /// ### Examples of incorrect code for this rule: + /// + /// ```JavaScript + /// var {} = foo; + /// var [] = foo; + /// var {a: {}} = foo; + /// var {a: []} = foo; + /// function foo({}) {} + /// function foo([]) {} + /// function foo({a: {}}) {} + /// function foo({a: []}) {} + /// ``` + /// + /// ### Examples of correct code for this rule: + /// + /// ```JavaScript + /// var {a = {}} = foo; + /// var {a = []} = foo; + /// function foo({a = {}}) {} + /// function foo({a = []}) {} + /// ``` + /// + NoEmptyPattern +); + +const RULE_NAME: &str = "no-empty-pattern"; + +impl Rule for NoEmptyPattern { + const NAME: &'static str = RULE_NAME; + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let (pattern_type, span) = match node.get().kind() { + AstKind::ArrayPattern(array) if array.elements.is_empty() => ("array", array.span), + AstKind::ObjectPattern(object) if object.properties.is_empty() => { + ("object", object.span) + } + _ => return, + }; + + ctx.diagnostic(NoEmptyPatternDiagnostic(pattern_type, span)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var {a = {}} = foo;", None), + ("var {a, b = {}} = foo;", None), + ("var {a = []} = foo;", None), + ("function foo({a = {}}) {}", None), + ("function foo({a = []}) {}", None), + ("var [a] = foo", None), + ]; + + let fail = vec![ + ("var {} = foo", None), + ("var [] = foo", None), + ("var {a: {}} = foo", None), + ("var {a, b: {}} = foo", None), + ("var {a: []} = foo", None), + ("function foo({}) {}", None), + ("function foo([]) {}", None), + ("function foo({a: {}}) {}", None), + ("function foo({a: []}) {}", None), + ]; + + Tester::new(RULE_NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_empty_pattern.snap b/crates/oxc_linter/src/snapshots/no_empty_pattern.snap new file mode 100644 index 000000000..dbbcc6598 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_empty_pattern.snap @@ -0,0 +1,68 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_empty_pattern +--- + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ var {} = foo + · ─┬ + · ╰── Empty object binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ var [] = foo + · ─┬ + · ╰── Empty array binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ var {a: {}} = foo + · ─┬ + · ╰── Empty object binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ var {a, b: {}} = foo + · ─┬ + · ╰── Empty object binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ var {a: []} = foo + · ─┬ + · ╰── Empty array binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ function foo({}) {} + · ─┬ + · ╰── Empty object binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ function foo([]) {} + · ─┬ + · ╰── Empty array binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ function foo({a: {}}) {} + · ─┬ + · ╰── Empty object binding pattern + ╰──── + + × eslint(no-empty-pattern): Disallow empty destructuring patterns + ╭─[no_empty_pattern.tsx:1:1] + 1 │ function foo({a: []}) {} + · ─┬ + · ╰── Empty array binding pattern + ╰──── +