From c344144a1b2f599768232d27e28f085a6508626c Mon Sep 17 00:00:00 2001 From: wenzhe Date: Sun, 14 Apr 2024 10:30:48 +0800 Subject: [PATCH] feat(linter/tree-shaking): support Class --- .../listener_map.rs | 102 ++++++++++++++- .../no_side_effects_in_initialization/mod.rs | 80 ++++++------ .../no_side_effects_in_initialization.snap | 120 ++++++++++++++++++ 3 files changed, 257 insertions(+), 45 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 a9648405bf8a2..c111e25c58758 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 @@ -3,11 +3,12 @@ use std::cell::{Cell, RefCell}; use oxc_ast::{ ast::{ Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentTarget, - BinaryExpression, BindingPattern, BindingPatternKind, CallExpression, - ComputedMemberExpression, Declaration, Expression, FormalParameter, Function, + BinaryExpression, BindingPattern, BindingPatternKind, CallExpression, Class, ClassBody, + ClassElement, ComputedMemberExpression, Declaration, Expression, FormalParameter, Function, IdentifierReference, MemberExpression, ModuleDeclaration, NewExpression, - ParenthesizedExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget, - Statement, StaticMemberExpression, ThisExpression, VariableDeclarator, + ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey, + SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression, + VariableDeclarator, }, AstKind, }; @@ -133,6 +134,9 @@ impl<'a> ListenerMap for AstNode<'a> { function.report_effects_when_called(options); options.has_valid_this.set(old_val); } + AstKind::Class(class) => { + class.report_effects_when_called(options); + } _ => {} } } @@ -161,16 +165,98 @@ impl<'a> ListenerMap for AstNode<'a> { impl<'a> ListenerMap for Declaration<'a> { fn report_effects(&self, options: &NodeListenerOptions) { - #[allow(clippy::single_match)] match self { Self::VariableDeclaration(decl) => { decl.declarations.iter().for_each(|decl| decl.report_effects(options)); } + Self::ClassDeclaration(decl) => { + decl.report_effects(options); + } _ => {} } } } +impl<'a> ListenerMap for Class<'a> { + fn report_effects(&self, options: &NodeListenerOptions) { + if let Some(super_class) = &self.super_class { + super_class.report_effects(options); + } + self.body.report_effects(options); + } + fn report_effects_when_called(&self, options: &NodeListenerOptions) { + if let Some(super_class) = &self.super_class { + super_class.report_effects_when_called(options); + } + self.body.report_effects_when_called(options); + } +} + +impl<'a> ListenerMap for ClassBody<'a> { + fn report_effects(&self, options: &NodeListenerOptions) { + self.body.iter().for_each(|class_element| { + class_element.report_effects(options); + }); + } + fn report_effects_when_called(&self, options: &NodeListenerOptions) { + let constructor = self.body.iter().find(|class_element| { + if let ClassElement::MethodDefinition(definition) = class_element { + return definition.kind.is_constructor(); + } + false + }); + + if let Some(constructor) = constructor { + constructor.report_effects_when_called(options); + } + + self.body + .iter() + .filter(|class_element| matches!(class_element, ClassElement::PropertyDefinition(_))) + .for_each(|property_definition| { + property_definition.report_effects_when_called(options); + }); + } +} + +impl<'a> ListenerMap for ClassElement<'a> { + fn report_effects(&self, options: &NodeListenerOptions) { + match self { + Self::MethodDefinition(method) => { + method.key.report_effects(options); + } + Self::PropertyDefinition(prop) => { + prop.key.report_effects(options); + } + _ => {} + } + } + fn report_effects_when_called(&self, options: &NodeListenerOptions) { + match self { + Self::MethodDefinition(method) => { + method.value.report_effects_when_called(options); + } + Self::PropertyDefinition(prop) => { + if let Some(value) = &prop.value { + value.report_effects_when_called(options); + } + } + _ => {} + } + } +} + +impl<'a> ListenerMap for PropertyKey<'a> { + fn report_effects(&self, options: &NodeListenerOptions) { + match self { + Self::Expression(expr) => { + expr.report_effects(options); + } + _ => no_effects(), + } + } +} + impl<'a> ListenerMap for VariableDeclarator<'a> { fn report_effects(&self, options: &NodeListenerOptions) { self.id.report_effects(options); @@ -230,6 +316,9 @@ impl<'a> ListenerMap for Expression<'a> { Self::BinaryExpression(expr) => { expr.get_value_and_report_effects(options); } + Self::ClassExpression(expr) => { + expr.report_effects(options); + } Self::ArrowFunctionExpression(_) | Self::FunctionExpression(_) | Self::Identifier(_) @@ -281,6 +370,9 @@ impl<'a> ListenerMap for Expression<'a> { Self::ParenthesizedExpression(expr) => { expr.report_effects_when_called(options); } + Self::ClassExpression(expr) => { + expr.report_effects_when_called(options); + } _ => { // Default behavior options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span())); 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 dd61b2e7b953e..82c9749757467 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 @@ -140,24 +140,24 @@ fn test() { "try {} catch (error) {}", "const x = ()=>{}; try {} catch (error) {const x = ext}; x()", "const x = ext; try {} catch (error) {const x = ()=>{}; x()}", - // // ClassBody - // "class x {a(){ext()}}", - // // ClassBody when called - // "class x {a(){ext()}}; const y = new x()", - // "class x {constructor(){}}; const y = new x()", - // "class y{}; class x extends y{}; const z = new x()", - // // ClassDeclaration - // "class x extends ext {}", - // // ClassDeclaration when called - // "class x {}; const y = new x()", - // // ClassExpression - // "const x = class extends ext {}", - // // ClassExpression when called - // "const x = new (class {})()", - // // ClassProperty - // "class x {y}", - // "class x {y = 1}", - // "class x {y = ext()}", + // ClassBody + "class x {a(){ext()}}", + // ClassBody when called + "class x {a(){ext()}}; const y = new x()", + "class x {constructor(){}}; const y = new x()", + "class y{}; class x extends y{}; const z = new x()", + // ClassDeclaration + "class x extends ext {}", + // ClassDeclaration when called + "class x {}; const y = new x()", + // ClassExpression + "const x = class extends ext {}", + // ClassExpression when called + "const x = new (class {})()", + // ClassProperty + "class x {y}", + "class x {y = 1}", + "class x {y = ext()}", // // ConditionalExpression // "const x = ext ? 1 : 2", // "const x = true ? 1 : ext()", @@ -411,32 +411,32 @@ fn test() { // TODO: check global function `ext` call when called `x()` in no strict mode // "var x=()=>{}; try {} catch (error) {var x=ext}; x()", // // ClassBody - // "class x {[ext()](){}}", + "class x {[ext()](){}}", // // 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()", + "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 - // "class x extends ext() {}", - // "class x {[ext()](){}}", + "class x extends ext() {}", + "class x {[ext()](){}}", // // 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()", + "class x {constructor(){ext()}}; new x()", + "class x {constructor(){ext()}}; const y = new x()", + "class x extends ext {}; const y = new x()", // // ClassExpression - // "const x = class extends ext() {}", - // "const x = class {[ext()](){}}", - // // ClassExpression when called - // "new (class {constructor(){ext()}})()", - // "const x = new (class {constructor(){ext()}})()", - // "const x = new (class extends ext {})()", - // // ClassProperty - // "class x {[ext()] = 1}", - // // ClassProperty when called - // "class x {y = ext()}; new x()", + "const x = class extends ext() {}", + "const x = class {[ext()](){}}", + // ClassExpression when called + "new (class {constructor(){ext()}})()", + "const x = new (class {constructor(){ext()}})()", + "const x = new (class extends ext {})()", + // ClassProperty + "class x {[ext()] = 1}", + // ClassProperty when called + "class x {y = ext()}; new x()", // // ConditionalExpression // "const x = ext() ? 1 : 2", // "const x = ext ? ext() : 2", 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 13a01aedd7d57..26a2acb3cb742 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 @@ -182,6 +182,126 @@ 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 │ class 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:24] + 1 │ class x {constructor(){ext()}}; 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:24] + 1 │ class x {constructor(){ext()}}; const y = 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:17] + 1 │ class x extends ext {}; const y = 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:24] + 1 │ class y {constructor(){ext()}}; class x extends y {}; 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: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()}}; 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:17] + 1 │ class x extends 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 │ class 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:24] + 1 │ class x {constructor(){ext()}}; 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:24] + 1 │ class x {constructor(){ext()}}; const y = 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:17] + 1 │ class x extends ext {}; const y = 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:25] + 1 │ const x = class extends 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 │ const x = class {[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 │ new (class {constructor(){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:37] + 1 │ const x = new (class {constructor(){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:30] + 1 │ const x = new (class extends 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 │ class x {[ext()] = 1} + · ─── + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling function return value + ╭─[no_side_effects_in_initialization.tsx:1:14] + 1 │ class x {y = ext()}; 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:15] 1 │ const x = new ext()