feat(linter/tree-shaking): support ConditionalExpression (#2965)

This commit is contained in:
Wang Wenzhe 2024-04-14 13:41:44 +08:00 committed by GitHub
parent da5ea411dd
commit 5b02ae1175
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 26 deletions

View file

@ -4,9 +4,9 @@ use oxc_ast::{
ast::{
Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentTarget,
BinaryExpression, BindingPattern, BindingPatternKind, CallExpression, Class, ClassBody,
ClassElement, ComputedMemberExpression, Declaration, Expression, FormalParameter, Function,
IdentifierReference, MemberExpression, ModuleDeclaration, NewExpression,
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey,
ClassElement, ComputedMemberExpression, ConditionalExpression, Declaration, Expression,
FormalParameter, Function, IdentifierReference, MemberExpression, ModuleDeclaration,
NewExpression, ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey,
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression,
VariableDeclarator,
},
@ -101,6 +101,24 @@ impl<'a> ListenerMap for Statement<'a> {
Self::BlockStatement(stmt) => {
stmt.body.iter().for_each(|stmt| stmt.report_effects(options));
}
Self::IfStatement(stmt) => {
let test_result = stmt.test.get_value_and_report_effects(options);
if let Some(falsy) = test_result.get_falsy_value() {
if falsy {
if let Some(alternate) = &stmt.alternate {
alternate.report_effects(options);
}
} else {
stmt.consequent.report_effects(options);
}
} else {
stmt.consequent.report_effects(options);
if let Some(alternate) = &stmt.alternate {
alternate.report_effects(options);
}
}
}
_ => {}
}
}
@ -319,6 +337,9 @@ impl<'a> ListenerMap for Expression<'a> {
Self::ClassExpression(expr) => {
expr.report_effects(options);
}
Self::ConditionalExpression(expr) => {
expr.get_value_and_report_effects(options);
}
Self::ArrowFunctionExpression(_)
| Self::FunctionExpression(_)
| Self::Identifier(_)
@ -373,6 +394,7 @@ impl<'a> ListenerMap for Expression<'a> {
Self::ClassExpression(expr) => {
expr.report_effects_when_called(options);
}
Self::ConditionalExpression(expr) => expr.report_effects_when_called(options),
_ => {
// Default behavior
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
@ -385,6 +407,8 @@ impl<'a> ListenerMap for Expression<'a> {
| Self::StringLiteral(_)
| Self::NumericLiteral(_)
| Self::TemplateLiteral(_) => Value::new(self),
Self::BinaryExpression(expr) => expr.get_value_and_report_effects(options),
Self::ConditionalExpression(expr) => expr.get_value_and_report_effects(options),
_ => {
self.report_effects(options);
Value::Unknown
@ -407,6 +431,38 @@ fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
)
}
impl<'a> ListenerMap for ConditionalExpression<'a> {
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
let test_result = self.test.get_value_and_report_effects(options);
if let Some(falsy) = test_result.get_falsy_value() {
if falsy {
self.alternate.get_value_and_report_effects(options)
} else {
self.consequent.get_value_and_report_effects(options)
}
} else {
self.consequent.report_effects(options);
self.alternate.report_effects(options);
test_result
}
}
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
let test_result = self.test.get_value_and_report_effects(options);
if let Some(falsy) = test_result.get_falsy_value() {
if falsy {
self.alternate.report_effects_when_called(options);
} else {
self.consequent.report_effects_when_called(options);
}
} else {
self.consequent.report_effects_when_called(options);
self.alternate.report_effects_when_called(options);
}
}
}
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);

View file

@ -158,19 +158,19 @@ fn test() {
"class x {y}",
"class x {y = 1}",
"class x {y = ext()}",
// // ConditionalExpression
// "const x = ext ? 1 : 2",
// "const x = true ? 1 : ext()",
// "const x = false ? ext() : 2",
// "if (true ? false : true) ext()",
// "ext ? 1 : ext.x",
// "ext ? ext.x : 1",
// ConditionalExpression
"const x = ext ? 1 : 2",
"const x = true ? 1 : ext()",
"const x = false ? ext() : 2",
"if (true ? false : true) ext()",
"ext ? 1 : ext.x",
"ext ? ext.x : 1",
// // ConditionalExpression when called
// "const x = ()=>{}, y = ()=>{};(ext ? x : y)()",
// "const x = ()=>{}; (true ? x : ext)()",
// "const x = ()=>{}; (false ? ext : x)()",
// // ContinueStatement
// "while(true){continue}",
"const x = ()=>{}, y = ()=>{};(ext ? x : y)()",
"const x = ()=>{}; (true ? x : ext)()",
"const x = ()=>{}; (false ? ext : x)()",
// ContinueStatement
"while(true){continue}",
// // DoWhileStatement
// "do {} while(true)",
// "do {} while(ext > 0)",
@ -410,23 +410,23 @@ fn test() {
"try {} catch (error) {ext()}",
// TODO: check global function `ext` call when called `x()` in no strict mode
// "var x=()=>{}; try {} catch (error) {var x=ext}; x()",
// // ClassBody
// ClassBody
"class x {[ext()](){}}",
// // ClassBody when called
// ClassBody when called
"class x {constructor(){ext()}}; new x()",
"class x {constructor(){ext()}}; const y = new x()",
"class x extends ext {}; const y = new x()",
"class y {constructor(){ext()}}; class x extends y {}; const z = new x()",
"class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()",
"class y{}; class x extends y{constructor(){super()}}; const z = new x()",
// // ClassDeclaration
// ClassDeclaration
"class x extends ext() {}",
"class x {[ext()](){}}",
// // ClassDeclaration when called
// ClassDeclaration when called
"class x {constructor(){ext()}}; new x()",
"class x {constructor(){ext()}}; const y = new x()",
"class x extends ext {}; const y = new x()",
// // ClassExpression
// ClassExpression
"const x = class extends ext() {}",
"const x = class {[ext()](){}}",
// ClassExpression when called
@ -437,11 +437,11 @@ fn test() {
"class x {[ext()] = 1}",
// ClassProperty when called
"class x {y = ext()}; new x()",
// // ConditionalExpression
// "const x = ext() ? 1 : 2",
// "const x = ext ? ext() : 2",
// "const x = ext ? 1 : ext()",
// "if (false ? false : true) ext()",
// ConditionalExpression
"const x = ext() ? 1 : 2",
"const x = ext ? ext() : 2",
"const x = ext ? 1 : ext()",
"if (false ? false : true) ext()",
// // ConditionalExpression when called
// "const x = ()=>{}; (true ? ext : x)()",
// "const x = ()=>{}; (false ? x : ext)()",

View file

@ -302,6 +302,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:11]
1 │ const x = ext() ? 1 : 2
· ───
╰────
⚠ 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:17]
1 │ const x = ext ? ext() : 2
· ───
╰────
⚠ 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:21]
1 │ const x = ext ? 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:27]
1 │ if (false ? false : true) 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:15]
1 │ const x = new ext()

View file

@ -5,7 +5,7 @@ use oxc_syntax::operator::BinaryOperator;
use crate::LintContext;
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum Value {
Boolean(bool),
Number(f64),
@ -14,6 +14,7 @@ pub enum Value {
}
// We only care if it is falsy value (empty string).
#[derive(Copy, Clone)]
pub enum StringValue {
Empty,
NonEmpty,
@ -43,6 +44,14 @@ impl Value {
_ => Value::Unknown,
}
}
pub fn get_falsy_value(&self) -> Option<bool> {
match &self {
Value::Unknown => None,
Value::Boolean(boolean) => Some(!*boolean),
Value::Number(num) => Some(*num == 0.0),
Value::String(str) => Some(matches!(str, StringValue::Empty)),
}
}
}
pub fn get_write_expr<'a, 'b>(
@ -93,6 +102,10 @@ pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value)
}
_ => Value::Unknown,
},
BinaryOperator::Subtraction => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a - b),
_ => Value::Unknown,
},
_ => Value::Unknown,
}
}