oxc/crates/oxc_syntax/src/operator.rs
DonIsaac b952942993 feat(linter): add eslint/no-unused-vars ( attempt 3.2) (#4445)
> Re-creation of #4427 due to rebasing issues. Original attempt: #642
-----

Third time's the charm?

Each time I attempt this rule, I find a bunch of bugs in `Semantic`, and I expect this attempt to be no different. Expect sidecar issues+PRs stemming from this PR here.

## Not Supported
These are cases supported in the original eslint rule, but that I'm intentionally deciding not to support
- export comments in scripts
  ```js
  /* exported a */ var a;
  ```
- global comments
  ```js
  /* global a */ var a;
   ```

## Behavior Changes
These are intentional deviations from the original rule's behavior:
- logical re-assignments are not considered usages
  ```js
  // passes in eslint/no-unused-vars, fails in this implementation
  let a = 0; a ||= 1;
  let b = 0; b &&= 2;
  let c = undefined; c ??= []
  ```

## Known Limitations
- Lint rules do not have babel or tsconfig information, meaning we can't determine if `React` imports are being used or not. The relevant tsconfig settings here are `jsx`, `jsxPragma`, and `jsxFragmentName`. To accommodate this, all imports to symbols named `React` or `h` are ignored in JSX files.
- References to symbols used in JSDoc `{@link}` tags are not created, so symbols that are only used in doc comments will be reported as unused. See: #4443
- `.vue` files are skipped completely, since variables can be used in templates in ways we cannot detect
  > note: `.d.ts` files are skipped as well.

## Todo
- [x] Skip unused TS enum members on used enums
- [x] Skip unused parameters followed by used variables in object/array spreads
- [x] Re-assignments to array/object spreads do not respect `destructuredArrayIgnorePattern` (related to: https://github.com/oxc-project/oxc/issues/4435)
- [x] #4493
- [x] References inside a nested scope are not considered usages (#4447)
- [x] Port over typescript-eslint test cases _(wip, they've been copied and I'm slowly enabling them)_
- [x] Handle constructor properties
  ```ts
  class Foo {
    constructor(public a) {} // `a` should be allowed
  }
  ```
- [x] Read references in sequence expressions (that are not in the last position) should not count as a usage
  ```js
  let a = 0; let b = (a++, 0); console.log(b)
  ```
  > Honestly, is anyone even writing code like this?
- [x] function overload signatures should not be reported
- [x] Named functions returned from other functions get incorrectly reported as unused (found by @camc314)
  ```js
  function foo() {
    return function bar() { }
  }
  Foo()()
  ```
- [x] false positive for TS modules within ambient modules
  ```ts
  declare global {
    // incorrectly marked as unused
    namespace jest { }
  }
  ```

## Blockers
- https://github.com/oxc-project/oxc/issues/4436
- https://github.com/oxc-project/oxc/issues/4437
- #4446
- #4447
- #4494
- #4495

## Non-Blocking Issues
- #4443
- #4475 (prevents checks on exported symbols from namespaces)
2024-07-31 03:22:16 +00:00

378 lines
12 KiB
Rust

// Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]`
#![allow(non_snake_case)]
#[cfg(feature = "serialize")]
use serde::Serialize;
#[cfg(feature = "serialize")]
use tsify::Tsify;
use crate::precedence::{GetPrecedence, Precedence};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
pub enum AssignmentOperator {
#[cfg_attr(feature = "serialize", serde(rename = "="))]
Assign,
#[cfg_attr(feature = "serialize", serde(rename = "+="))]
Addition,
#[cfg_attr(feature = "serialize", serde(rename = "-="))]
Subtraction,
#[cfg_attr(feature = "serialize", serde(rename = "*="))]
Multiplication,
#[cfg_attr(feature = "serialize", serde(rename = "/="))]
Division,
#[cfg_attr(feature = "serialize", serde(rename = "%="))]
Remainder,
#[cfg_attr(feature = "serialize", serde(rename = "<<="))]
ShiftLeft,
#[cfg_attr(feature = "serialize", serde(rename = ">>="))]
ShiftRight,
#[cfg_attr(feature = "serialize", serde(rename = ">>>="))]
ShiftRightZeroFill,
#[cfg_attr(feature = "serialize", serde(rename = "|="))]
BitwiseOR,
#[cfg_attr(feature = "serialize", serde(rename = "^="))]
BitwiseXOR,
#[cfg_attr(feature = "serialize", serde(rename = "&="))]
BitwiseAnd,
#[cfg_attr(feature = "serialize", serde(rename = "&&="))]
LogicalAnd,
#[cfg_attr(feature = "serialize", serde(rename = "||="))]
LogicalOr,
#[cfg_attr(feature = "serialize", serde(rename = "??="))]
LogicalNullish,
#[cfg_attr(feature = "serialize", serde(rename = "**="))]
Exponential,
}
impl AssignmentOperator {
pub fn is_logical(self) -> bool {
matches!(self, Self::LogicalAnd | Self::LogicalOr | Self::LogicalNullish)
}
#[rustfmt::skip]
pub fn is_arithmetic(self) -> bool {
matches!(self, Self::Addition | Self::Subtraction | Self::Multiplication
| Self::Division | Self::Remainder | Self::Exponential
)
}
#[rustfmt::skip]
pub fn is_bitwise(self) -> bool {
matches!(self, Self::BitwiseOR | Self::BitwiseXOR | Self::BitwiseAnd
| Self::ShiftLeft | Self::ShiftRight | Self::ShiftRightZeroFill
)
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Assign => "=",
Self::Addition => "+=",
Self::Subtraction => "-=",
Self::Multiplication => "*=",
Self::Division => "/=",
Self::Remainder => "%=",
Self::ShiftLeft => "<<=",
Self::ShiftRight => ">>=",
Self::ShiftRightZeroFill => ">>>=",
Self::BitwiseOR => "|=",
Self::BitwiseXOR => "^=",
Self::BitwiseAnd => "&=",
Self::LogicalAnd => "&&=",
Self::LogicalOr => "||=",
Self::LogicalNullish => "??=",
Self::Exponential => "**=",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
pub enum BinaryOperator {
#[cfg_attr(feature = "serialize", serde(rename = "=="))]
Equality,
#[cfg_attr(feature = "serialize", serde(rename = "!="))]
Inequality,
#[cfg_attr(feature = "serialize", serde(rename = "==="))]
StrictEquality,
#[cfg_attr(feature = "serialize", serde(rename = "!=="))]
StrictInequality,
#[cfg_attr(feature = "serialize", serde(rename = "<"))]
LessThan,
#[cfg_attr(feature = "serialize", serde(rename = "<="))]
LessEqualThan,
#[cfg_attr(feature = "serialize", serde(rename = ">"))]
GreaterThan,
#[cfg_attr(feature = "serialize", serde(rename = ">="))]
GreaterEqualThan,
#[cfg_attr(feature = "serialize", serde(rename = "<<"))]
ShiftLeft,
#[cfg_attr(feature = "serialize", serde(rename = ">>"))]
ShiftRight,
#[cfg_attr(feature = "serialize", serde(rename = ">>>"))]
ShiftRightZeroFill,
#[cfg_attr(feature = "serialize", serde(rename = "+"))]
Addition,
#[cfg_attr(feature = "serialize", serde(rename = "-"))]
Subtraction,
#[cfg_attr(feature = "serialize", serde(rename = "*"))]
Multiplication,
#[cfg_attr(feature = "serialize", serde(rename = "/"))]
Division,
#[cfg_attr(feature = "serialize", serde(rename = "%"))]
Remainder,
#[cfg_attr(feature = "serialize", serde(rename = "|"))]
BitwiseOR,
#[cfg_attr(feature = "serialize", serde(rename = "^"))]
BitwiseXOR,
#[cfg_attr(feature = "serialize", serde(rename = "&"))]
BitwiseAnd,
#[cfg_attr(feature = "serialize", serde(rename = "in"))]
In,
#[cfg_attr(feature = "serialize", serde(rename = "instanceof"))]
Instanceof,
#[cfg_attr(feature = "serialize", serde(rename = "**"))]
Exponential,
}
impl BinaryOperator {
#[rustfmt::skip]
pub fn is_equality(self) -> bool {
matches!(self, Self::Equality | Self::Inequality | Self::StrictEquality | Self::StrictInequality)
}
#[rustfmt::skip]
pub fn is_compare(self) -> bool {
matches!(self, Self::LessThan | Self::LessEqualThan | Self::GreaterThan | Self::GreaterEqualThan)
}
#[rustfmt::skip]
pub fn is_arithmetic(self) -> bool {
matches!(self, Self::Addition | Self::Subtraction | Self::Multiplication
| Self::Division | Self::Remainder | Self::Exponential)
}
pub fn is_multiplicative(self) -> bool {
matches!(self, Self::Multiplication | Self::Division | Self::Remainder)
}
pub fn is_relational(self) -> bool {
matches!(self, Self::In | Self::Instanceof)
}
pub fn is_in(self) -> bool {
matches!(self, Self::In)
}
#[rustfmt::skip]
pub fn is_bitwise(self) -> bool {
self.is_bitshift() || matches!(self, Self::BitwiseOR | Self::BitwiseXOR | Self::BitwiseAnd)
}
pub fn is_bitshift(self) -> bool {
matches!(self, Self::ShiftLeft | Self::ShiftRight | Self::ShiftRightZeroFill)
}
pub fn is_numeric_or_string_binary_operator(self) -> bool {
self.is_arithmetic() || self.is_bitwise()
}
pub fn is_keyword(self) -> bool {
matches!(self, Self::In | Self::Instanceof)
}
pub fn compare_inverse_operator(self) -> Option<Self> {
match self {
Self::LessThan => Some(Self::GreaterThan),
Self::LessEqualThan => Some(Self::GreaterEqualThan),
Self::GreaterThan => Some(Self::LessThan),
Self::GreaterEqualThan => Some(Self::LessEqualThan),
_ => None,
}
}
pub fn equality_inverse_operator(self) -> Option<Self> {
match self {
Self::Equality => Some(Self::Inequality),
Self::Inequality => Some(Self::Equality),
Self::StrictEquality => Some(Self::StrictInequality),
Self::StrictInequality => Some(Self::StrictEquality),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Equality => "==",
Self::Inequality => "!=",
Self::StrictEquality => "===",
Self::StrictInequality => "!==",
Self::LessThan => "<",
Self::LessEqualThan => "<=",
Self::GreaterThan => ">",
Self::GreaterEqualThan => ">=",
Self::ShiftLeft => "<<",
Self::ShiftRight => ">>",
Self::ShiftRightZeroFill => ">>>",
Self::Addition => "+",
Self::Subtraction => "-",
Self::Multiplication => "*",
Self::Division => "/",
Self::Remainder => "%",
Self::BitwiseOR => "|",
Self::BitwiseXOR => "^",
Self::BitwiseAnd => "&",
Self::In => "in",
Self::Instanceof => "instanceof",
Self::Exponential => "**",
}
}
pub fn lower_precedence(&self) -> Precedence {
match self {
Self::BitwiseOR => Precedence::LogicalAnd,
Self::BitwiseXOR => Precedence::BitwiseOr,
Self::BitwiseAnd => Precedence::BitwiseXor,
Self::Equality | Self::Inequality | Self::StrictEquality | Self::StrictInequality => {
Precedence::BitwiseAnd
}
Self::LessThan
| Self::LessEqualThan
| Self::GreaterThan
| Self::GreaterEqualThan
| Self::Instanceof
| Self::In => Precedence::Equals,
Self::ShiftLeft | Self::ShiftRight | Self::ShiftRightZeroFill => Precedence::Compare,
Self::Addition | Self::Subtraction => Precedence::Shift,
Self::Multiplication | Self::Remainder | Self::Division => Precedence::Add,
Self::Exponential => Precedence::Multiply,
}
}
}
impl GetPrecedence for BinaryOperator {
fn precedence(&self) -> Precedence {
match self {
Self::BitwiseOR => Precedence::BitwiseOr,
Self::BitwiseXOR => Precedence::BitwiseXor,
Self::BitwiseAnd => Precedence::BitwiseAnd,
Self::Equality | Self::Inequality | Self::StrictEquality | Self::StrictInequality => {
Precedence::Equals
}
Self::LessThan
| Self::LessEqualThan
| Self::GreaterThan
| Self::GreaterEqualThan
| Self::Instanceof
| Self::In => Precedence::Compare,
Self::ShiftLeft | Self::ShiftRight | Self::ShiftRightZeroFill => Precedence::Shift,
Self::Subtraction | Self::Addition => Precedence::Add,
Self::Multiplication | Self::Remainder | Self::Division => Precedence::Multiply,
Self::Exponential => Precedence::Exponentiation,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "serialize", derive(Tsify))]
pub enum LogicalOperator {
#[cfg_attr(feature = "serialize", serde(rename = "||"))]
Or,
#[cfg_attr(feature = "serialize", serde(rename = "&&"))]
And,
#[cfg_attr(feature = "serialize", serde(rename = "??"))]
Coalesce,
}
impl LogicalOperator {
pub fn as_str(&self) -> &'static str {
match self {
Self::Or => "||",
Self::And => "&&",
Self::Coalesce => "??",
}
}
}
impl GetPrecedence for LogicalOperator {
fn precedence(&self) -> Precedence {
match self {
Self::Or => Precedence::LogicalOr,
Self::And => Precedence::LogicalAnd,
Self::Coalesce => Precedence::NullishCoalescing,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "serialize", derive(Tsify))]
pub enum UnaryOperator {
#[cfg_attr(feature = "serialize", serde(rename = "-"))]
UnaryNegation,
#[cfg_attr(feature = "serialize", serde(rename = "+"))]
UnaryPlus,
#[cfg_attr(feature = "serialize", serde(rename = "!"))]
LogicalNot,
#[cfg_attr(feature = "serialize", serde(rename = "~"))]
BitwiseNot,
#[cfg_attr(feature = "serialize", serde(rename = "typeof"))]
Typeof,
#[cfg_attr(feature = "serialize", serde(rename = "void"))]
Void,
#[cfg_attr(feature = "serialize", serde(rename = "delete"))]
Delete,
}
impl UnaryOperator {
pub fn is_arithmetic(self) -> bool {
matches!(self, Self::UnaryNegation | Self::UnaryPlus)
}
/// Returns `true` if this operator is a [`LogicalNot`].
///
/// [`LogicalNot`]: UnaryOperator::LogicalNot
pub fn is_not(self) -> bool {
matches!(self, Self::LogicalNot)
}
pub fn is_bitwise(self) -> bool {
matches!(self, Self::BitwiseNot)
}
pub fn is_keyword(self) -> bool {
matches!(self, Self::Typeof | Self::Void | Self::Delete)
}
pub fn as_str(&self) -> &'static str {
match self {
Self::UnaryNegation => "-",
Self::UnaryPlus => "+",
Self::LogicalNot => "!",
Self::BitwiseNot => "~",
Self::Typeof => "typeof",
Self::Void => "void",
Self::Delete => "delete",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "serialize", derive(Tsify))]
pub enum UpdateOperator {
#[cfg_attr(feature = "serialize", serde(rename = "++"))]
Increment,
#[cfg_attr(feature = "serialize", serde(rename = "--"))]
Decrement,
}
impl UpdateOperator {
pub fn as_str(&self) -> &'static str {
match self {
Self::Increment => "++",
Self::Decrement => "--",
}
}
}