feat(linter/tree-shaking): support While/Switch/Yield Statement (#3155)

All test cases passed now. There are something remain. I'll change the category when it's ready.

- [ ] Port [`isPureFuncion`](96f0d1c825/src/utils/helpers.ts (L53-L67)), [pure-function](198432ecc9/src/utils/pure-functions.ts) 
- [ ] Support [options](463fa1f0be/src/rules/no-side-effects-in-initialization.ts (L1130-L1138))
- [ ] Clean TODO
- [ ] Add more support of operator computation. [The original version](463fa1f0be/src/rules/no-side-effects-in-initialization.ts (L139-L210)) is straightforward benefit of JS. We only [support Number](f91a063616/crates/oxc_linter/src/utils/tree_shaking.rs (L194)) now.
This commit is contained in:
Wang Wenzhe 2024-05-03 23:21:28 +08:00 committed by GitHub
parent 8290421e72
commit 32df6d779c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 256 additions and 64 deletions

View file

@ -10,8 +10,8 @@ use oxc_ast::{
JSXExpressionContainer, JSXFragment, JSXIdentifier, JSXOpeningElement, LogicalExpression,
MemberExpression, ModuleExportName, NewExpression, ObjectExpression, ObjectPropertyKind,
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey, SequenceExpression,
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression, UnaryExpression,
VariableDeclarator,
SimpleAssignmentTarget, Statement, StaticMemberExpression, SwitchCase, ThisExpression,
UnaryExpression, VariableDeclarator,
},
AstKind,
};
@ -183,6 +183,23 @@ impl<'a> ListenerMap for Statement<'a> {
Self::LabeledStatement(stmt) => {
stmt.body.report_effects(options);
}
Self::WhileStatement(stmt) => {
if stmt
.test
.get_value_and_report_effects(options)
.get_falsy_value()
.is_some_and(|is_falsy| is_falsy)
{
return;
}
stmt.body.report_effects(options);
}
Self::SwitchStatement(stmt) => {
stmt.discriminant.report_effects(options);
stmt.cases.iter().for_each(|case| {
case.report_effects(options);
});
}
_ => {}
}
}
@ -537,6 +554,20 @@ impl<'a> ListenerMap for Expression<'a> {
Self::SequenceExpression(expr) => {
expr.get_value_and_report_effects(options);
}
Self::YieldExpression(expr) => {
expr.argument.iter().for_each(|arg| arg.report_effects(options));
}
Self::TaggedTemplateExpression(expr) => {
expr.tag.report_effects_when_called(options);
expr.quasi.expressions.iter().for_each(|expr| {
expr.report_effects(options);
});
}
Self::TemplateLiteral(expr) => {
expr.expressions.iter().for_each(|expr| {
expr.report_effects(options);
});
}
Self::ArrowFunctionExpression(_)
| Self::FunctionExpression(_)
| Self::Identifier(_)
@ -641,6 +672,17 @@ fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
)
}
impl<'a> ListenerMap for SwitchCase<'a> {
fn report_effects(&self, options: &NodeListenerOptions) {
if let Some(test) = &self.test {
test.report_effects(options);
}
self.consequent.iter().for_each(|stmt| {
stmt.report_effects(options);
});
}
}
impl<'a> ListenerMap for SequenceExpression<'a> {
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
let mut val = Value::Unknown;

View file

@ -339,22 +339,22 @@ fn test() {
// SequenceExpression
"let x = 1; x++, x++",
"if (ext, false) ext()",
// // SwitchCase
// "switch(ext){case ext:const x = 1;break;default:}",
// // SwitchStatement
// "switch(ext){}",
// "const x = ()=>{}; switch(ext){case 1:const x = ext}; x()",
// "const x = ext; switch(ext){case 1:const x = ()=>{}; x()}",
// // TaggedTemplateExpression
// "const x = ()=>{}; const y = x``",
// // TemplateLiteral
// "const x = ``",
// "const x = `Literal`",
// "const x = `Literal ${ext}`",
// r#"const x = ()=>"a"; const y = `Literal ${x()}`"#,
// // ThisExpression
// SwitchCase
"switch(ext){case ext:const x = 1;break;default:}",
// SwitchStatement
"switch(ext){}",
"const x = ()=>{}; switch(ext){case 1:const x = ext}; x()",
"const x = ext; switch(ext){case 1:const x = ()=>{}; x()}",
// TaggedTemplateExpression
"const x = ()=>{}; const y = x``",
// TemplateLiteral
"const x = ``",
"const x = `Literal`",
"const x = `Literal ${ext}`",
r#"const x = ()=>"a"; const y = `Literal ${x()}`"#,
// ThisExpression
"const y = this.x",
// // ThisExpression when mutated
// ThisExpression when mutated
"const y = new (function (){this.x = 1})()",
"const y = new (function (){{this.x = 1}})()",
"const y = new (function (){(()=>{this.x = 1})()})()",
@ -370,23 +370,23 @@ fn test() {
// UpdateExpression
"let x=1;x++",
"const x = {};x.y++",
// // VariableDeclaration
// "const x = 1",
// // VariableDeclarator
// "var x, y",
// "var x = 1, y = 2",
// "const x = 1, y = 2",
// "let x = 1, y = 2",
// "const {x} = {}",
// // WhileStatement
// "while(true){}",
// "while(ext > 0){}",
// "const x = ()=>{}; while(true)x()",
// // YieldExpression
// "function* x(){const a = yield}; x()",
// "function* x(){yield ext}; x()",
// // Supports TypeScript nodes
// "interface Blub {}",
// VariableDeclaration
"const x = 1",
// VariableDeclarator
"var x, y",
"var x = 1, y = 2",
"const x = 1, y = 2",
"let x = 1, y = 2",
"const {x} = {}",
// WhileStatement
"while(true){}",
"while(ext > 0){}",
"const x = ()=>{}; while(true)x()",
// YieldExpression
"function* x(){const a = yield}; x()",
"function* x(){yield ext}; x()",
// Supports TypeScript nodes
"interface Blub {}",
];
let fail = vec![
@ -622,22 +622,22 @@ fn test() {
"1, ext()",
"if (1, true) ext()",
"if (1, ext) ext()",
// // Super when called
// "class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()",
// "class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()",
// "class y{}; class x extends y{constructor(){super()}}; const z = new x()",
// // SwitchCase
// "switch(ext){case ext():}",
// "switch(ext){case 1:ext()}",
// // SwitchStatement
// "switch(ext()){}",
// Super when called
"class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()",
"class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()",
"class y{}; class x extends y{constructor(){super()}}; const z = new x()",
// SwitchCase
"switch(ext){case ext():}",
"switch(ext){case 1:ext()}",
// SwitchStatement
"switch(ext()){}",
// "var x=()=>{}; switch(ext){case 1:var x=ext}; x()",
// // TaggedTemplateExpression
// "const x = ext``",
// "ext``",
// "const x = ()=>{}; const y = x`${ext()}`",
// // TemplateLiteral
// "const x = `Literal ${ext()}`",
// TaggedTemplateExpression
"const x = ext``",
"ext``",
"const x = ()=>{}; const y = x`${ext()}`",
// TemplateLiteral
"const x = `Literal ${ext()}`",
// ThisExpression when mutated
"this.x = 1",
"(()=>{this.x = 1})()",
@ -657,21 +657,21 @@ fn test() {
// UpdateExpression
"ext++",
"const x = {};x[ext()]++",
// // VariableDeclaration
// "const x = ext()",
// // VariableDeclarator
// "var x = ext(),y = ext()",
// "const x = ext(),y = ext()",
// "let x = ext(),y = ext()",
// "const {x = ext()} = {}",
// // WhileStatement
// "while(ext()){}",
// "while(true)ext()",
// "while(true){ext()}",
// // YieldExpression
// "function* x(){yield ext()}; x()",
// // YieldExpression when called
// "function* x(){yield ext()}; x()"
// VariableDeclaration
"const x = ext()",
// VariableDeclarator
"var x = ext(),y = ext()",
"const x = ext(),y = ext()",
"let x = ext(),y = ext()",
"const {x = ext()} = {}",
// WhileStatement
"while(ext()){}",
"while(true)ext()",
"while(true){ext()}",
// YieldExpression
"function* x(){yield ext()}; x()",
// YieldExpression when called
"function* x(){yield ext()}; x()"
];
Tester::new(NoSideEffectsInInitialization::NAME, pass, fail).test_and_snapshot();

View file

@ -1070,6 +1070,78 @@ 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:24]
1 │ class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
╭─[no_side_effects_in_initialization.tsx:1:66]
1 │ class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()
· ─────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
╭─[no_side_effects_in_initialization.tsx:1:44]
1 │ class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()
· ─────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
╭─[no_side_effects_in_initialization.tsx:1:53]
1 │ class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()
· ─────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
╭─[no_side_effects_in_initialization.tsx:1:44]
1 │ class y{}; class x extends y{constructor(){super()}}; const z = new 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:18]
1 │ switch(ext){case 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:20]
1 │ switch(ext){case 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:8]
1 │ switch(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``
· ───
╰────
⚠ 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:1]
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:33]
1 │ const x = ()=>{}; const y = x`${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:22]
1 │ const x = `Literal ${ext()}`
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ this.x = 1
@ -1153,3 +1225,81 @@ expression: no_side_effects_in_initialization
1 │ const x = {};x[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()
· ───
╰────
⚠ 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:9]
1 │ var x = ext(),y = 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:19]
1 │ var x = ext(),y = 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(),y = 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:21]
1 │ const x = ext(),y = 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:9]
1 │ let x = ext(),y = 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:19]
1 │ let x = ext(),y = 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:12]
1 │ const {x = 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 │ while(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:12]
1 │ while(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:13]
1 │ while(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:21]
1 │ function* x(){yield ext()}; 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:21]
1 │ function* x(){yield ext()}; x()
· ───
╰────