mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter/tree-shaking): support part BinaryExpression (#2922)
This commit is contained in:
parent
659e891615
commit
990eda61d7
4 changed files with 133 additions and 30 deletions
|
|
@ -3,11 +3,11 @@ use std::cell::{Cell, RefCell};
|
|||
use oxc_ast::{
|
||||
ast::{
|
||||
Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentTarget,
|
||||
BindingPattern, BindingPatternKind, CallExpression, ComputedMemberExpression, Declaration,
|
||||
Expression, FormalParameter, Function, IdentifierReference, MemberExpression,
|
||||
ModuleDeclaration, NewExpression, ParenthesizedExpression, PrivateFieldExpression, Program,
|
||||
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression,
|
||||
VariableDeclarator,
|
||||
BinaryExpression, BindingPattern, BindingPatternKind, CallExpression,
|
||||
ComputedMemberExpression, Declaration, Expression, FormalParameter, Function,
|
||||
IdentifierReference, MemberExpression, ModuleDeclaration, NewExpression,
|
||||
ParenthesizedExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget,
|
||||
Statement, StaticMemberExpression, ThisExpression, VariableDeclarator,
|
||||
},
|
||||
AstKind,
|
||||
};
|
||||
|
|
@ -17,7 +17,7 @@ use rustc_hash::FxHashSet;
|
|||
|
||||
use crate::{
|
||||
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
|
||||
utils::{get_write_expr, has_pure_notation, no_effects, Value},
|
||||
utils::{calculate_binary_operation, get_write_expr, has_pure_notation, no_effects, Value},
|
||||
LintContext,
|
||||
};
|
||||
|
||||
|
|
@ -51,8 +51,8 @@ pub trait ListenerMap {
|
|||
fn report_effects_when_assigned(&self, _options: &NodeListenerOptions) {}
|
||||
fn report_effects_when_called(&self, _options: &NodeListenerOptions) {}
|
||||
fn report_effects_when_mutated(&self, _options: &NodeListenerOptions) {}
|
||||
fn get_value_and_report_effects(&self, _options: &NodeListenerOptions) -> Option<Value> {
|
||||
None
|
||||
fn get_value_and_report_effects(&self, _options: &NodeListenerOptions) -> Value {
|
||||
Value::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +97,9 @@ impl<'a> ListenerMap for Statement<'a> {
|
|||
finalizer.body.iter().for_each(|stmt| stmt.report_effects(options));
|
||||
});
|
||||
}
|
||||
Self::BlockStatement(stmt) => {
|
||||
stmt.body.iter().for_each(|stmt| stmt.report_effects(options));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +227,9 @@ impl<'a> ListenerMap for Expression<'a> {
|
|||
Self::AwaitExpression(expr) => {
|
||||
expr.argument.report_effects(options);
|
||||
}
|
||||
Self::BinaryExpression(expr) => {
|
||||
expr.get_value_and_report_effects(options);
|
||||
}
|
||||
Self::ArrowFunctionExpression(_)
|
||||
| Self::FunctionExpression(_)
|
||||
| Self::Identifier(_)
|
||||
|
|
@ -281,6 +287,18 @@ impl<'a> ListenerMap for Expression<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||
match self {
|
||||
Self::BooleanLiteral(_)
|
||||
| Self::StringLiteral(_)
|
||||
| Self::NumericLiteral(_)
|
||||
| Self::TemplateLiteral(_) => Value::new(self),
|
||||
_ => {
|
||||
self.report_effects(options);
|
||||
Value::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// which kind of Expression defines `report_effects_when_called` method.
|
||||
|
|
@ -297,6 +315,14 @@ fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
impl<'a> ListenerMap for BinaryExpression<'a> {
|
||||
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||
let left = self.left.get_value_and_report_effects(options);
|
||||
let right = self.right.get_value_and_report_effects(options);
|
||||
calculate_binary_operation(self.operator, left, right)
|
||||
}
|
||||
}
|
||||
|
||||
impl ListenerMap for ThisExpression {
|
||||
fn report_effects_when_mutated(&self, options: &NodeListenerOptions) {
|
||||
if !options.has_valid_this.get() {
|
||||
|
|
@ -331,7 +357,7 @@ impl<'a> ListenerMap for ParenthesizedExpression<'a> {
|
|||
fn report_effects_when_mutated(&self, options: &NodeListenerOptions) {
|
||||
self.expression.report_effects_when_mutated(options);
|
||||
}
|
||||
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Option<Value> {
|
||||
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||
self.expression.get_value_and_report_effects(options)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,19 +99,19 @@ fn test() {
|
|||
"const x = []",
|
||||
"const x = [ext,ext]",
|
||||
"const x = [1,,2,]",
|
||||
// // ArrayPattern
|
||||
// ArrayPattern
|
||||
"const [x] = []",
|
||||
"const [,x,] = []",
|
||||
// // ArrowFunctionExpression
|
||||
// ArrowFunctionExpression
|
||||
"const x = a=>{a(); ext()}",
|
||||
// // ArrowFunctionExpression when called
|
||||
// ArrowFunctionExpression when called
|
||||
"(()=>{})()",
|
||||
"(a=>{})()",
|
||||
"((...a)=>{})()",
|
||||
"(({a})=>{})()",
|
||||
// // ArrowFunctionExpression when mutated
|
||||
// ArrowFunctionExpression when mutated
|
||||
"const x = ()=>{}; x.y = 1",
|
||||
// // AssignmentExpression
|
||||
// AssignmentExpression
|
||||
"var x;x = {}",
|
||||
"var x;x += 1",
|
||||
"const x = {}; x.y = 1",
|
||||
|
|
@ -124,19 +124,19 @@ fn test() {
|
|||
"const {x: y = ext} = {}",
|
||||
"const {[ext]: x = ext} = {}",
|
||||
"const x = ()=>{}, {y = x()} = {}",
|
||||
// // BinaryExpression
|
||||
// "const x = 1 + 2",
|
||||
// "if (1-1) ext()",
|
||||
// // BlockStatement
|
||||
// "{}",
|
||||
// "const x = ()=>{};{const x = ext}x()",
|
||||
// "const x = ext;{const x = ()=>{}; x()}",
|
||||
// // BreakStatement
|
||||
// "while(true){break}",
|
||||
// // CallExpression
|
||||
// BinaryExpression
|
||||
"const x = 1 + 2",
|
||||
"if (1-1) ext()",
|
||||
// BlockStatement
|
||||
"{}",
|
||||
"const x = ()=>{};{const x = ext}x()",
|
||||
"const x = ext;{const x = ()=>{}; x()}",
|
||||
// BreakStatement
|
||||
"while(true){break}",
|
||||
// CallExpression
|
||||
"(a=>{const y = a})(ext, ext)",
|
||||
"const x = ()=>{}, y = ()=>{}; x(y())",
|
||||
// // CatchClause
|
||||
// CatchClause
|
||||
"try {} catch (error) {}",
|
||||
"const x = ()=>{}; try {} catch (error) {const x = ext}; x()",
|
||||
"const x = ext; try {} catch (error) {const x = ()=>{}; x()}",
|
||||
|
|
@ -393,12 +393,12 @@ fn test() {
|
|||
// AwaitExpression
|
||||
"const x = async ()=>{await ext()}; x()",
|
||||
// // BinaryExpression
|
||||
// "const x = 1 + ext()",
|
||||
// "const x = ext() + 1",
|
||||
// // BlockStatement
|
||||
// "{ext()}",
|
||||
"const x = 1 + ext()",
|
||||
"const x = ext() + 1",
|
||||
// BlockStatement
|
||||
"{ext()}",
|
||||
// "var x=()=>{};{var x=ext}x()",
|
||||
// "var x=ext;{x(); var x=()=>{}}",
|
||||
"var x=ext;{x(); var x=()=>{}}",
|
||||
// CallExpression
|
||||
"(()=>{})(ext(), 1)",
|
||||
"(()=>{})(1, ext())",
|
||||
|
|
|
|||
|
|
@ -128,6 +128,30 @@ expression: no_side_effects_in_initialization
|
|||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
|
||||
╭─[no_side_effects_in_initialization.tsx:1:15]
|
||||
1 │ const x = 1 + ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
|
||||
╭─[no_side_effects_in_initialization.tsx:1:11]
|
||||
1 │ const x = ext() + 1
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
|
||||
╭─[no_side_effects_in_initialization.tsx:1:2]
|
||||
1 │ {ext()}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
|
||||
╭─[no_side_effects_in_initialization.tsx:1:7]
|
||||
1 │ var x=ext;{x(); var x=()=>{}}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
|
||||
╭─[no_side_effects_in_initialization.tsx:1:10]
|
||||
1 │ (()=>{})(ext(), 1)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use oxc_ast::{ast::Expression, AstKind};
|
||||
use oxc_semantic::AstNodeId;
|
||||
use oxc_span::Span;
|
||||
use oxc_syntax::operator::BinaryOperator;
|
||||
|
||||
use crate::LintContext;
|
||||
|
||||
|
|
@ -8,6 +9,40 @@ use crate::LintContext;
|
|||
pub enum Value {
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
String(StringValue),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// We only care if it is falsy value (empty string).
|
||||
pub enum StringValue {
|
||||
Empty,
|
||||
NonEmpty,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn new(expr: &Expression) -> Value {
|
||||
match expr {
|
||||
Expression::BooleanLiteral(bool_lit) => Value::Boolean(bool_lit.value),
|
||||
Expression::NumericLiteral(num_lit) => Value::Number(num_lit.value),
|
||||
Expression::StringLiteral(str_lit) => {
|
||||
if str_lit.value.is_empty() {
|
||||
Value::String(StringValue::Empty)
|
||||
} else {
|
||||
Value::String(StringValue::NonEmpty)
|
||||
}
|
||||
}
|
||||
Expression::TemplateLiteral(template_lit) => {
|
||||
if !template_lit.is_no_substitution_template() {
|
||||
Value::Unknown
|
||||
} else if template_lit.quasi().is_some_and(|s| s == "") {
|
||||
Value::String(StringValue::Empty)
|
||||
} else {
|
||||
Value::String(StringValue::NonEmpty)
|
||||
}
|
||||
}
|
||||
_ => Value::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_write_expr<'a, 'b>(
|
||||
|
|
@ -43,3 +78,21 @@ pub fn has_pure_notation(span: Span, ctx: &LintContext) -> bool {
|
|||
|
||||
raw.contains("@__PURE__") || raw.contains("#__PURE__")
|
||||
}
|
||||
|
||||
/// Port from https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/463fa1f0bef7caa2b231a38b9c3557051f506c92/src/rules/no-side-effects-in-initialization.ts#L136-L161
|
||||
pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value) -> Value {
|
||||
match op {
|
||||
BinaryOperator::Addition => match (left, right) {
|
||||
(Value::Number(a), Value::Number(b)) => Value::Number(a + b),
|
||||
(Value::String(str1), Value::String(str2)) => {
|
||||
if matches!(str1, StringValue::Empty) && matches!(str2, StringValue::Empty) {
|
||||
Value::String(StringValue::Empty)
|
||||
} else {
|
||||
Value::String(StringValue::NonEmpty)
|
||||
}
|
||||
}
|
||||
_ => Value::Unknown,
|
||||
},
|
||||
_ => Value::Unknown,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue