Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(es/ast): Add more utilities #9054

Merged
merged 5 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 69 additions & 10 deletions crates/swc_ecma_ast/src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#![allow(clippy::vec_box)]
use std::mem::transmute;

use is_macro::Is;
use string_enum::StringEnum;
use swc_atoms::Atom;
Expand Down Expand Up @@ -203,17 +205,60 @@ impl Expr {
}
}

/// Unwraps an expression with a given function.
///
/// If the provided function returns [Some], the function is called again
/// with the returned value. If the provided functions returns [None],
/// the last expression is returned.
pub fn unwrap_with<'a, F>(&'a self, mut op: F) -> &'a Expr
where
F: FnMut(&'a Expr) -> Option<&'a Expr>,
{
let mut cur = self;
loop {
match op(cur) {
Some(next) => cur = next,
None => return cur,
}
}
}

/// Unwraps an expression with a given function.
///
/// If the provided function returns [Some], the function is called again
/// with the returned value. If the provided functions returns [None],
/// the last expression is returned.
pub fn unwrap_mut_with<'a, F>(&'a mut self, mut op: F) -> &'a mut Expr
where
F: FnMut(&'a mut Expr) -> Option<&'a mut Expr>,
{
let mut cur = self;
loop {
match unsafe {
// Safety: Polonius is not yet stable
op(transmute::<&mut _, &mut _>(cur))
} {
Some(next) => cur = next,
None => {
return cur;
}
}
}
}

/// Normalize parenthesized expressions.
///
/// This will normalize `(foo)`, `((foo))`, ... to `foo`.
///
/// If `self` is not a parenthesized expression, it will be returned as is.
pub fn unwrap_parens(&self) -> &Expr {
let mut cur = self;
while let Expr::Paren(ref expr) = cur {
cur = &expr.expr;
}
cur
self.unwrap_with(|e| {
if let Expr::Paren(expr) = e {
Some(&expr.expr)
} else {
None
}
})
}

/// Normalize parenthesized expressions.
Expand All @@ -222,11 +267,25 @@ impl Expr {
///
/// If `self` is not a parenthesized expression, it will be returned as is.
pub fn unwrap_parens_mut(&mut self) -> &mut Expr {
let mut cur = self;
while let Expr::Paren(ref mut expr) = cur {
cur = &mut expr.expr;
}
cur
self.unwrap_mut_with(|e| {
if let Expr::Paren(expr) = e {
Some(&mut expr.expr)
} else {
None
}
})
}

/// Normalize sequences and parenthesized expressions.
///
/// This returns the last expression of a sequence expression or the
/// expression of a parenthesized expression.
pub fn unwrap_seqs_and_parens(&self) -> &Self {
self.unwrap_with(|expr| match expr {
Expr::Seq(SeqExpr { exprs, .. }) => exprs.last().map(|v| &**v),
Expr::Paren(ParenExpr { expr, .. }) => Some(expr),
_ => None,
})
}

/// Creates an expression from `exprs`. This will return first element if
Expand Down
25 changes: 25 additions & 0 deletions crates/swc_ecma_ast/src/module_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,31 @@ pub enum ImportSpecifier {
Namespace(ImportStarAsSpecifier),
}

impl ImportSpecifier {
pub fn is_type_only(&self) -> bool {
match self {
ImportSpecifier::Named(named) => named.is_type_only,
ImportSpecifier::Default(..) | ImportSpecifier::Namespace(..) => false,
}
}

pub fn local(&self) -> &Ident {
match self {
ImportSpecifier::Named(named) => &named.local,
ImportSpecifier::Default(default) => &default.local,
ImportSpecifier::Namespace(ns) => &ns.local,
}
}

pub fn local_mut(&mut self) -> &mut Ident {
match self {
ImportSpecifier::Named(named) => &mut named.local,
ImportSpecifier::Default(default) => &mut default.local,
ImportSpecifier::Namespace(ns) => &mut ns.local,
}
}
}

/// e.g. `import foo from 'mod.js'`
#[ast_node("ImportDefaultSpecifier")]
#[derive(Eq, Hash, EqIgnoreSpan)]
Expand Down
3 changes: 1 addition & 2 deletions crates/swc_ecma_lints/src/rules/constructor_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};

const BAD_SUPER_MESSAGE: &str = "Unexpected 'super()' because 'super' is not a constructor";
Expand Down Expand Up @@ -81,7 +80,7 @@ impl ConstructorSuper {

fn collect_class(&mut self, class: &Class) {
self.class_meta.super_class = match &class.super_class {
Some(super_class) => match unwrap_seqs_and_parens(super_class.as_ref()) {
Some(super_class) => match super_class.unwrap_seqs_and_parens() {
Expr::Ident(_) | Expr::Class(_) => SuperClass::Valid,
_ => SuperClass::Invalid,
},
Expand Down
6 changes: 2 additions & 4 deletions crates/swc_ecma_lints/src/rules/no_compare_neg_zero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};

pub fn no_compare_neg_zero(config: &RuleConfig<()>) -> Option<Box<dyn Rule>> {
Expand Down Expand Up @@ -46,10 +45,9 @@ impl NoCompareNegZero {
op: op!(unary, "-"),
arg,
..
}) = unwrap_seqs_and_parens(expr)
}) = expr.unwrap_seqs_and_parens()
{
if let Expr::Lit(Lit::Num(Number { value, .. })) = unwrap_seqs_and_parens(arg.as_ref())
{
if let Expr::Lit(Lit::Num(Number { value, .. })) = arg.unwrap_seqs_and_parens() {
return *value == 0f64;
}
}
Expand Down
5 changes: 2 additions & 3 deletions crates/swc_ecma_lints/src/rules/no_param_reassign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};

const INVALID_REGEX_MESSAGE: &str = "no-param-reassign: invalid regex pattern in allowPattern. Check syntax documentation https://docs.rs/regex/latest/regex/#syntax";
Expand Down Expand Up @@ -150,7 +149,7 @@ impl NoParamReassign {
return;
}

match unwrap_seqs_and_parens(member_expr.obj.as_ref()) {
match member_expr.obj.unwrap_seqs_and_parens() {
Expr::Ident(ident) => {
if self.is_satisfying_function_param(ident) {
self.emit_report(ident.span, &ident.sym);
Expand Down Expand Up @@ -190,7 +189,7 @@ impl NoParamReassign {
}

fn check_expr(&self, expr: &Expr) {
match unwrap_seqs_and_parens(expr) {
match expr.unwrap_seqs_and_parens() {
Expr::Ident(ident) => {
if self.is_satisfying_function_param(ident) {
self.emit_report(ident.span, &ident.sym);
Expand Down
5 changes: 2 additions & 3 deletions crates/swc_ecma_lints/src/rules/no_throw_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};

const EXPECTED_AN_ERROR_OBJECT: &str = "Expected an error object to be thrown";
Expand Down Expand Up @@ -52,7 +51,7 @@ impl NoThrowLiteral {

#[allow(clippy::only_used_in_recursion)]
fn could_be_error(&self, expr: &Expr) -> bool {
match unwrap_seqs_and_parens(expr) {
match expr.unwrap_seqs_and_parens() {
Expr::Ident(_)
| Expr::New(_)
| Expr::Call(_)
Expand Down Expand Up @@ -96,7 +95,7 @@ impl NoThrowLiteral {
}

fn check(&self, throw_stmt: &ThrowStmt) {
let arg = unwrap_seqs_and_parens(throw_stmt.arg.as_ref());
let arg = throw_stmt.arg.unwrap_seqs_and_parens();

if !self.could_be_error(arg) {
self.emit_report(throw_stmt.span, EXPECTED_AN_ERROR_OBJECT);
Expand Down
3 changes: 1 addition & 2 deletions crates/swc_ecma_lints/src/rules/prefer_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};

// todo: implement option destructuring: all | any
Expand Down Expand Up @@ -293,7 +292,7 @@ impl Visit for PreferConst {
}

fn visit_update_expr(&mut self, update_expr: &UpdateExpr) {
if let Expr::Ident(ident) = unwrap_seqs_and_parens(update_expr.arg.as_ref()) {
if let Expr::Ident(ident) = update_expr.arg.unwrap_seqs_and_parens() {
self.consider_mutation_for_ident(ident, false);
}

Expand Down
6 changes: 3 additions & 3 deletions crates/swc_ecma_lints/src/rules/prefer_regex_literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::{extract_arg_val, unwrap_seqs_and_parens, ArgValue},
rules::utils::{extract_arg_val, ArgValue},
};

const UNEXPECTED_REG_EXP_MESSAGE: &str =
Expand Down Expand Up @@ -90,14 +90,14 @@ impl PreferRegexLiterals {
if let Some(ExprOrSpread { expr, .. }) = args.first() {
self.first_arg = Some(extract_arg_val(
self.unresolved_ctxt,
unwrap_seqs_and_parens(expr.as_ref()),
expr.unwrap_seqs_and_parens(),
));
}

if let Some(ExprOrSpread { expr, .. }) = args.get(1) {
self.second_arg = Some(extract_arg_val(
self.unresolved_ctxt,
unwrap_seqs_and_parens(expr.as_ref()),
expr.unwrap_seqs_and_parens(),
));
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/swc_ecma_lints/src/rules/radix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::{extract_arg_val, unwrap_seqs_and_parens, ArgValue},
rules::utils::{extract_arg_val, ArgValue},
};

const OBJ_NAMES: &[&str] = &["Number", "globalThis"];
Expand Down Expand Up @@ -140,7 +140,7 @@ impl Radix {
match call_expr.args.get(1) {
Some(ExprOrSpread { expr, .. }) => {
let expr = if self.unwrap_parens_and_seqs {
unwrap_seqs_and_parens(expr.as_ref())
expr.unwrap_seqs_and_parens()
} else {
expr.as_ref()
};
Expand Down
4 changes: 2 additions & 2 deletions crates/swc_ecma_lints/src/rules/symbol_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::{extract_arg_val, unwrap_seqs_and_parens, ArgValue},
rules::utils::{extract_arg_val, ArgValue},
};

const SYMBOL_EXPECTED_MESSAGE: &str = "Expected Symbol to have a description";
Expand Down Expand Up @@ -62,7 +62,7 @@ impl SymbolDescription {
fn check(&self, span: Span, first_arg: Option<&ExprOrSpread>) {
if let Some(ExprOrSpread { expr, .. }) = first_arg {
if self.enforce_string_description {
match extract_arg_val(self.unresolved_ctxt, unwrap_seqs_and_parens(expr)) {
match extract_arg_val(self.unresolved_ctxt, expr.unwrap_seqs_and_parens()) {
ArgValue::Str(_) => {}
_ => {
self.emit_report(span, SYMBOL_STRING_DESCRIPTION_EXPECTED_MESSAGE);
Expand Down
12 changes: 1 addition & 11 deletions crates/swc_ecma_lints/src/rules/utils.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use serde::{Deserialize, Serialize};
use swc_atoms::Atom;
use swc_common::SyntaxContext;
use swc_ecma_ast::{
Expr, Lit, MemberExpr, MemberProp, Number, ParenExpr, Regex, SeqExpr, Str, TaggedTpl, Tpl,
};
use swc_ecma_ast::{Expr, Lit, MemberExpr, MemberProp, Number, Regex, Str, TaggedTpl, Tpl};

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
Expand Down Expand Up @@ -93,11 +91,3 @@ pub fn extract_arg_val(unresolved_ctxt: SyntaxContext, expr: &Expr) -> ArgValue
_ => ArgValue::Other,
}
}

pub fn unwrap_seqs_and_parens(expr: &Expr) -> &Expr {
match expr {
Expr::Seq(SeqExpr { exprs, .. }) => unwrap_seqs_and_parens(exprs.last().unwrap()),
Expr::Paren(ParenExpr { expr, .. }) => unwrap_seqs_and_parens(expr.as_ref()),
_ => expr,
}
}
Loading