Skip to content

Commit

Permalink
feat: improve SideEffectDetector (#497)
Browse files Browse the repository at this point in the history
<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->

### Test Plan

<!-- e.g. is there anything you'd like reviewers to focus on? -->

---
  • Loading branch information
hyf0 committed Mar 9, 2024
1 parent 89c243b commit 04a1e3c
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 1 deletion.
2 changes: 1 addition & 1 deletion crates/rolldown/src/ast_scanner/impl_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::{side_effect_detector::SideEffectDetector, AstScanner};
impl<'ast> AstScanner<'ast> {
fn visit_top_level_stmt(&mut self, stmt: &oxc::ast::ast::Statement<'ast>) {
self.current_stmt_info.side_effect =
SideEffectDetector { scope: self.scope }.detect_side_effect_of_stmt(stmt);
SideEffectDetector::new(self.scope).detect_side_effect_of_stmt(stmt);
self.visit_statement(stmt);
}
}
Expand Down
98 changes: 98 additions & 0 deletions crates/rolldown/src/ast_scanner/side_effect_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub struct SideEffectDetector<'a> {
}

impl<'a> SideEffectDetector<'a> {
pub fn new(scope: &'a AstScope) -> Self {
Self { scope }
}

fn is_unresolved_reference(&self, ident_ref: &IdentifierReference) -> bool {
self.scope.is_unresolved(ident_ref.reference_id.get().unwrap())
}
Expand Down Expand Up @@ -103,6 +107,30 @@ impl<'a> SideEffectDetector<'a> {
| Expression::FunctionExpression(_)
| Expression::ArrowFunctionExpression(_)
| Expression::StringLiteral(_) => false,
Expression::ObjectExpression(obj_expr) => {
obj_expr.properties.iter().any(|obj_prop| match obj_prop {
oxc::ast::ast::ObjectPropertyKind::ObjectProperty(prop) => {
let key_side_effect = match &prop.key {
oxc::ast::ast::PropertyKey::Identifier(_)
| oxc::ast::ast::PropertyKey::PrivateIdentifier(_) => false,
oxc::ast::ast::PropertyKey::Expression(expr) => self.detect_side_effect_of_expr(expr),
};

let prop_init_side_effect =
prop.init.as_ref().map_or(false, |expr| self.detect_side_effect_of_expr(expr));

let value_side_effect = self.detect_side_effect_of_expr(&prop.value);

key_side_effect || prop_init_side_effect || value_side_effect
}
oxc::ast::ast::ObjectPropertyKind::SpreadProperty(spread) => {
self.detect_side_effect_of_expr(&spread.argument)
}
})
}
Expression::UnaryExpression(unary_expr) => {
self.detect_side_effect_of_expr(&unary_expr.argument)
}
Expression::MemberExpression(mem_expr) => Self::detect_side_effect_of_member_expr(mem_expr),
Expression::ClassExpression(cls) => self.detect_side_effect_of_class(cls),
// Accessing global variables considered as side effect.
Expand Down Expand Up @@ -188,3 +216,73 @@ impl<'a> SideEffectDetector<'a> {
}
}
}

#[cfg(test)]
mod test {
use oxc::span::SourceType;
use rolldown_common::AstScope;
use rolldown_oxc_utils::OxcCompiler;

use crate::ast_scanner::side_effect_detector::SideEffectDetector;

fn assert_statements_no_side_effect(code: &str) {
let source_type = SourceType::default()
.with_always_strict(true)
.with_module(true)
.with_jsx(true)
.with_typescript(false);
let program = OxcCompiler::parse(code, source_type);

let ast_scope = {
let semantic = program.make_semantic(source_type);
let (mut symbol_table, scope) = semantic.into_symbol_table_and_scope_tree();
AstScope::new(scope, std::mem::take(&mut symbol_table.references))
};

let has_side_effect = program
.program()
.body
.iter()
.any(|stmt| SideEffectDetector::new(&ast_scope).detect_side_effect_of_stmt(stmt));

assert!(!has_side_effect, "expect\n```js\n{code}\n```\nnot have any side effect");
}

#[test]
fn test_side_effect() {
assert_statements_no_side_effect("export { a }");
assert_statements_no_side_effect("const a = {}");
assert_statements_no_side_effect(
"const PatchFlags = {
'TEXT':1,
'1':'TEXT',
'CLASS':2,
'2':'CLASS',
'STYLE':4,
'4':'STYLE',
'PROPS':8,
'8':'PROPS',
'FULL_PROPS':16,
'16':'FULL_PROPS',
'NEED_HYDRATION':32,
'32':'NEED_HYDRATION',
'STABLE_FRAGMENT':64,
'64':'STABLE_FRAGMENT',
'KEYED_FRAGMENT':128,
'128':'KEYED_FRAGMENT',
'UNKEYED_FRAGMENT':256,
'256':'UNKEYED_FRAGMENT',
'NEED_PATCH':512,
'512':'NEED_PATCH',
'DYNAMIC_SLOTS':1024,
'1024':'DYNAMIC_SLOTS',
'DEV_ROOT_FRAGMENT':2048,
'2048':'DEV_ROOT_FRAGMENT',
'HOISTED': -1,
'-1':'HOISTED',
'BAIL': -2,
'-2':'BAIL'
};",
);
}
}

0 comments on commit 04a1e3c

Please sign in to comment.