Repository: swc-project/swc
File: crates/swc_ecma_transformer/src/regexp.rs (Rust)

Code before:
        || options.unicode_property_regex
        || options.unicode_regex
        || options.unicode_sets_regex
    {
        Some(RegexpPass { options })
    } else {
        None
    }
}

struct RegexpPass {
    options: RegExpOptions,
}

impl VisitMutHook<TraverseCtx> for RegexpPass {
    fn exit_expr(&mut self, expr: &mut Expr, _: &mut TraverseCtx) {
        if let Expr::Lit(Lit::Regex(regex)) = expr {
            if (self.options.dot_all_regex && regex.flags.contains('s'))
                || (self.options.sticky_regex && regex.flags.contains('y'))
                || (self.options.unicode_regex && regex.flags.contains('u'))
                || (self.options.unicode_sets_regex && regex.flags.contains('v'))
                || (self.options.has_indices && regex.flags.contains('d'))
                || (self.options.named_capturing_groups_regex && regex.exp.contains("(?<"))
                || (self.options.lookbehind_assertion && regex.exp.contains("(?<=")
                    || regex.exp.contains("(?<!"))
                || (self.options.unicode_property_regex
                    && (regex.exp.contains("\\p{") || regex.exp.contains("\\P{")))
            {
                let Regex { exp, flags, span } = regex.take();

                let exp: Expr = exp.into();
                let mut args = vec![exp.into()];

                if !flags.is_empty() {
                    let flags: Expr = flags.into();
                    args.push(flags.into());
                }

                *expr = CallExpr {
                    span,
                    callee: quote_ident!("RegExp").as_callee(),
                    args,
                    ..Default::default()
                }
                .into()
            }
        }
    }
}


Review comment (suggestion):
The logical operator precedence issue causes `regex.exp.contains("(?<!")` to always trigger transformation regardless of the `lookbehind_assertion` option. The condition should be:
```rust
|| (self.options.lookbehind_assertion && (regex.exp.contains("(?<=") || regex.exp.contains("(?<!")))
```
This ensures both lookbehind patterns are only transformed when the option is enabled.
```suggestion
                || (self.options.lookbehind_assertion && (regex.exp.contains("(?<=") || regex.exp.contains("(?<!")))
```

Code after:
        || options.sticky_regex
        || options.unicode_property_regex
        || options.unicode_regex
        || options.unicode_sets_regex
    {
        Some(RegexpPass { options })
    } else {
        None
    }
}

struct RegexpPass {
    options: RegExpOptions,
}

impl VisitMutHook<TraverseCtx> for RegexpPass {
    fn exit_expr(&mut self, expr: &mut Expr, _: &mut TraverseCtx) {
        if let Expr::Lit(Lit::Regex(regex)) = expr {
            if (self.options.dot_all_regex && regex.flags.contains('s'))
                || (self.options.sticky_regex && regex.flags.contains('y'))
                || (self.options.unicode_regex && regex.flags.contains('u'))
                || (self.options.unicode_sets_regex && regex.flags.contains('v'))
                || (self.options.has_indices && regex.flags.contains('d'))
                || (self.options.named_capturing_groups_regex && regex.exp.contains("(?<"))
                || (self.options.lookbehind_assertion && regex.exp.contains("(?<=")
                    || regex.exp.contains("(?<!"))
                || (self.options.unicode_property_regex
                    && (regex.exp.contains("\\p{") || regex.exp.contains("\\P{")))
            {
                let Regex { exp, flags, span } = regex.take();

                let exp: Expr = exp.into();
                let mut args = vec![exp.into()];

                if !flags.is_empty() {
                    let flags: Expr = flags.into();
                    args.push(flags.into());
                }

                *expr = CallExpr {
                    span,
                    callee: quote_ident!("RegExp").as_callee(),
                    args,
                    ..Default::default()
                }
                .into()
            }
        }
    }
}