From 32df6d779cb8d4e066cf3f20f3c670edb8fc8890 Mon Sep 17 00:00:00 2001 From: Wang Wenzhe Date: Fri, 3 May 2024 23:21:28 +0800 Subject: [PATCH] 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`](https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/96f0d1c8258732070894887a30ffa42d2952e3fc/src/utils/helpers.ts#L53-L67), [pure-function](https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/198432ecc9b341d10d20639e83b68055f3d72d13/src/utils/pure-functions.ts) - [ ] Support [options](https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/463fa1f0bef7caa2b231a38b9c3557051f506c92/src/rules/no-side-effects-in-initialization.ts#L1130-L1138) - [ ] Clean TODO - [ ] Add more support of operator computation. [The original version](https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/463fa1f0bef7caa2b231a38b9c3557051f506c92/src/rules/no-side-effects-in-initialization.ts#L139-L210) is straightforward benefit of JS. We only [support Number](https://github.com/oxc-project/oxc/blob/f91a063616b70b2fb7c1e8966d57620ee025d810/crates/oxc_linter/src/utils/tree_shaking.rs#L194) now. --- .../listener_map.rs | 46 +++++- .../no_side_effects_in_initialization/mod.rs | 124 +++++++-------- .../no_side_effects_in_initialization.snap | 150 ++++++++++++++++++ 3 files changed, 256 insertions(+), 64 deletions(-) diff --git a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs index f14f1255e..8077a1a1d 100644 --- a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs +++ b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs @@ -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; diff --git a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs index d3031074d..89bb784bd 100644 --- a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs +++ b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs @@ -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(); diff --git a/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap b/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap index ef32f319f..e0103da98 100644 --- a/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap +++ b/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap @@ -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() + · ─── + ╰────