1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2020 the Deno authors. All rights reserved. MIT license.
use super::Context;
use super::LintRule;
use swc_ecmascript::ast::{ArrayPat, ObjectPat, ObjectPatProp};
use swc_ecmascript::visit::noop_visit_type;
use swc_ecmascript::visit::Node;
use swc_ecmascript::visit::Visit;

pub struct NoEmptyPattern;

impl LintRule for NoEmptyPattern {
  fn new() -> Box<Self> {
    Box::new(NoEmptyPattern)
  }

  fn tags(&self) -> &[&'static str] {
    &["recommended"]
  }

  fn code(&self) -> &'static str {
    "no-empty-pattern"
  }

  fn lint_module(
    &self,
    context: &mut Context,
    module: &swc_ecmascript::ast::Module,
  ) {
    let mut visitor = NoEmptyPatternVisitor::new(context);
    visitor.visit_module(module, module);
  }
}

struct NoEmptyPatternVisitor<'c> {
  context: &'c mut Context,
}

impl<'c> NoEmptyPatternVisitor<'c> {
  fn new(context: &'c mut Context) -> Self {
    Self { context }
  }
}

impl<'c> Visit for NoEmptyPatternVisitor<'c> {
  noop_visit_type!();

  fn visit_object_pat_prop(
    &mut self,
    obj_pat_prop: &ObjectPatProp,
    _parent: &dyn Node,
  ) {
    if let ObjectPatProp::KeyValue(kv_prop) = obj_pat_prop {
      if let swc_ecmascript::ast::Pat::Object(obj_pat) = &*kv_prop.value {
        self.visit_object_pat(obj_pat, _parent);
      } else if let swc_ecmascript::ast::Pat::Array(arr_pat) = &*kv_prop.value {
        self.visit_array_pat(arr_pat, _parent);
      }
    }
  }

  fn visit_object_pat(&mut self, obj_pat: &ObjectPat, _parent: &dyn Node) {
    if obj_pat.props.is_empty() {
      if obj_pat.type_ann.is_none() {
        self.context.add_diagnostic(
          obj_pat.span,
          "no-empty-pattern",
          "empty patterns are not allowed",
        )
      }
    } else {
      for prop in &obj_pat.props {
        self.visit_object_pat_prop(prop, _parent)
      }
    }
  }

  fn visit_array_pat(&mut self, arr_pat: &ArrayPat, _parent: &dyn Node) {
    if arr_pat.elems.is_empty() {
      self.context.add_diagnostic(
        arr_pat.span,
        "no-empty-pattern",
        "empty patterns are not allowed",
      )
    } else {
      for elem in &arr_pat.elems {
        if let Some(element) = elem {
          if let swc_ecmascript::ast::Pat::Object(obj_pat) = element {
            self.visit_object_pat(&obj_pat, _parent);
          } else if let swc_ecmascript::ast::Pat::Array(arr_pat) = element {
            self.visit_array_pat(&arr_pat, _parent);
          }
        }
      }
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::test_util::*;

  #[test]
  fn no_empty_pattern_valid() {
    assert_lint_ok_n::<NoEmptyPattern>(vec![
      "const {a = {}} = foo;",
      "const {a, b = {}} = foo;",
      "const {a = []} = foo;",
      "function foo({a = {}}) {}",
      "function foo({a = []}) {}",
      "var [a] = foo",
      "async function startFileServerAsLibrary({}: FileServerCfg = {}): Promise<void>",
    ]);
  }

  #[test]
  fn no_empty_pattern_invalid() {
    assert_lint_err::<NoEmptyPattern>("const {} = foo", 6);
    assert_lint_err::<NoEmptyPattern>("const [] = foo", 6);
    assert_lint_err::<NoEmptyPattern>("const {a: {}} = foo", 10);
    assert_lint_err::<NoEmptyPattern>("const {a, b: {}} = foo", 13);
    assert_lint_err::<NoEmptyPattern>("const {a: []} = foo", 10);
    assert_lint_err::<NoEmptyPattern>("function foo({}) {}", 13);
    assert_lint_err::<NoEmptyPattern>("function foo([]) {}", 13);
    assert_lint_err::<NoEmptyPattern>("function foo({a: {}}) {}", 17);
    assert_lint_err::<NoEmptyPattern>("function foo({a: []}) {}", 17);
  }
}