Skip to content

Commit

Permalink
feat(linter/tree-shaking): support Class
Browse files Browse the repository at this point in the history
  • Loading branch information
mysteryven committed Apr 14, 2024
1 parent bd9fc6d commit c344144
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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);
}
_ => {}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(_)
Expand Down Expand Up @@ -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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()",
Expand Down Expand Up @@ -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",
Expand Down
120 changes: 120 additions & 0 deletions crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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]
1class 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]
1class 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()
Expand Down

0 comments on commit c344144

Please sign in to comment.