Skip to content

Commit

Permalink
feat(linter): add oxc/no-map-spread (oxc-project#6751)
Browse files Browse the repository at this point in the history
Adds a new performance rule that disallows using object and array spreads in `map` and `flatMap` to modify/merge arrays and objects.
I've currently added it to `nursery` so we can battle-harden it before making it generally available as a `perf` rule.

```ts
// needless O(n) spread on `x`, making this an O(n^2) operation
const x = arr.map(x => {
  return { ...x, foo: getFoo(x.id) }
})

// Object.assign is better here
const x = arr.map(x => {
  return Object.assign(x, { foo: getFoo(x.id) })
})
```
  • Loading branch information
DonIsaac authored and Orenbek committed Oct 28, 2024
1 parent 8c4b28e commit b9e8f51
Show file tree
Hide file tree
Showing 5 changed files with 1,207 additions and 0 deletions.
8 changes: 8 additions & 0 deletions crates/oxc_ast/src/ast_impl/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,14 @@ impl<'a> ArrayExpressionElement<'a> {
}
}

impl<'a> ObjectPropertyKind<'a> {
/// Returns `true` if this object property is a [spread](SpreadElement).
#[inline]
pub fn is_spread(&self) -> bool {
matches!(self, Self::SpreadProperty(_))
}
}

impl<'a> PropertyKey<'a> {
#[allow(missing_docs)]
pub fn static_name(&self) -> Option<Cow<'a, str>> {
Expand Down
32 changes: 32 additions & 0 deletions crates/oxc_linter/src/ast_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ pub fn nth_outermost_paren_parent<'a, 'b>(
.filter(|parent| !matches!(parent.kind(), AstKind::ParenthesizedExpression(_)))
.nth(n)
}

/// Iterate over parents of `node`, skipping nodes that are also ignored by
/// [`Expression::get_inner_expression`].
pub fn iter_outer_expressions<'a, 's>(
Expand Down Expand Up @@ -424,3 +425,34 @@ pub fn get_function_like_declaration<'b>(

decl.id.get_binding_identifier()
}

/// Get the first identifier reference within a member expression chain or
/// standalone reference.
///
/// For example, when called on the right-hand side of this [`AssignmentExpression`]:
/// ```ts
/// let x = a
/// // ^
/// let y = a.b.c
/// // ^
/// ```
///
/// As this function walks down the member expression chain, if no identifier
/// reference is found, it returns [`Err`] with the leftmost expression.
/// ```ts
/// let x = 1 + 1
/// // ^^^^^ Err(BinaryExpression)
/// let y = this.foo.bar
/// // ^^^^ Err(ThisExpression)
/// ```
pub fn leftmost_identifier_reference<'a, 'b: 'a>(
expr: &'b Expression<'a>,
) -> Result<&'a IdentifierReference<'a>, &'b Expression<'a>> {
match expr {
Expression::Identifier(ident) => Ok(ident.as_ref()),
Expression::StaticMemberExpression(mem) => leftmost_identifier_reference(&mem.object),
Expression::ComputedMemberExpression(mem) => leftmost_identifier_reference(&mem.object),
Expression::PrivateFieldExpression(mem) => leftmost_identifier_reference(&mem.object),
_ => Err(expr),
}
}
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ mod oxc {
pub mod no_async_endpoint_handlers;
pub mod no_barrel_file;
pub mod no_const_enum;
pub mod no_map_spread;
pub mod no_optional_chaining;
pub mod no_rest_spread_properties;
pub mod number_arg_out_of_range;
Expand Down Expand Up @@ -765,6 +766,7 @@ oxc_macros::declare_all_lint_rules! {
oxc::no_async_endpoint_handlers,
oxc::no_barrel_file,
oxc::no_const_enum,
oxc::no_map_spread,
oxc::no_optional_chaining,
oxc::no_rest_spread_properties,
oxc::number_arg_out_of_range,
Expand Down
Loading

0 comments on commit b9e8f51

Please sign in to comment.