mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 20:28:58 +00:00
feat(linter/tree-shaking): support Class (#2964)
This commit is contained in:
parent
bd9fc6d169
commit
da5ea411dd
3 changed files with 257 additions and 45 deletions
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue