mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter/tree-shaking): support ConditionalExpression (#2965)
This commit is contained in:
parent
da5ea411dd
commit
5b02ae1175
4 changed files with 119 additions and 26 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)()",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue