mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 13:18:59 +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::{
|
use oxc_ast::{
|
||||||
ast::{
|
ast::{
|
||||||
Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentTarget,
|
Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentTarget,
|
||||||
BinaryExpression, BindingPattern, BindingPatternKind, CallExpression,
|
BinaryExpression, BindingPattern, BindingPatternKind, CallExpression, Class, ClassBody,
|
||||||
ComputedMemberExpression, Declaration, Expression, FormalParameter, Function,
|
ClassElement, ComputedMemberExpression, Declaration, Expression, FormalParameter, Function,
|
||||||
IdentifierReference, MemberExpression, ModuleDeclaration, NewExpression,
|
IdentifierReference, MemberExpression, ModuleDeclaration, NewExpression,
|
||||||
ParenthesizedExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget,
|
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey,
|
||||||
Statement, StaticMemberExpression, ThisExpression, VariableDeclarator,
|
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression,
|
||||||
|
VariableDeclarator,
|
||||||
},
|
},
|
||||||
AstKind,
|
AstKind,
|
||||||
};
|
};
|
||||||
|
|
@ -133,6 +134,9 @@ impl<'a> ListenerMap for AstNode<'a> {
|
||||||
function.report_effects_when_called(options);
|
function.report_effects_when_called(options);
|
||||||
options.has_valid_this.set(old_val);
|
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> {
|
impl<'a> ListenerMap for Declaration<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
match self {
|
match self {
|
||||||
Self::VariableDeclaration(decl) => {
|
Self::VariableDeclaration(decl) => {
|
||||||
decl.declarations.iter().for_each(|decl| decl.report_effects(options));
|
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> {
|
impl<'a> ListenerMap for VariableDeclarator<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
self.id.report_effects(options);
|
self.id.report_effects(options);
|
||||||
|
|
@ -230,6 +316,9 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::BinaryExpression(expr) => {
|
Self::BinaryExpression(expr) => {
|
||||||
expr.get_value_and_report_effects(options);
|
expr.get_value_and_report_effects(options);
|
||||||
}
|
}
|
||||||
|
Self::ClassExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
Self::ArrowFunctionExpression(_)
|
Self::ArrowFunctionExpression(_)
|
||||||
| Self::FunctionExpression(_)
|
| Self::FunctionExpression(_)
|
||||||
| Self::Identifier(_)
|
| Self::Identifier(_)
|
||||||
|
|
@ -281,6 +370,9 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::ParenthesizedExpression(expr) => {
|
Self::ParenthesizedExpression(expr) => {
|
||||||
expr.report_effects_when_called(options);
|
expr.report_effects_when_called(options);
|
||||||
}
|
}
|
||||||
|
Self::ClassExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Default behavior
|
// Default behavior
|
||||||
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
|
||||||
|
|
|
||||||
|
|
@ -140,24 +140,24 @@ fn test() {
|
||||||
"try {} catch (error) {}",
|
"try {} catch (error) {}",
|
||||||
"const x = ()=>{}; try {} catch (error) {const x = ext}; x()",
|
"const x = ()=>{}; try {} catch (error) {const x = ext}; x()",
|
||||||
"const x = ext; try {} catch (error) {const x = ()=>{}; x()}",
|
"const x = ext; try {} catch (error) {const x = ()=>{}; x()}",
|
||||||
// // ClassBody
|
// ClassBody
|
||||||
// "class x {a(){ext()}}",
|
"class x {a(){ext()}}",
|
||||||
// // ClassBody when called
|
// ClassBody when called
|
||||||
// "class x {a(){ext()}}; const y = new x()",
|
"class x {a(){ext()}}; const y = new x()",
|
||||||
// "class x {constructor(){}}; const y = new x()",
|
"class x {constructor(){}}; const y = new x()",
|
||||||
// "class y{}; class x extends y{}; const z = new x()",
|
"class y{}; class x extends y{}; const z = new x()",
|
||||||
// // ClassDeclaration
|
// ClassDeclaration
|
||||||
// "class x extends ext {}",
|
"class x extends ext {}",
|
||||||
// // ClassDeclaration when called
|
// ClassDeclaration when called
|
||||||
// "class x {}; const y = new x()",
|
"class x {}; const y = new x()",
|
||||||
// // ClassExpression
|
// ClassExpression
|
||||||
// "const x = class extends ext {}",
|
"const x = class extends ext {}",
|
||||||
// // ClassExpression when called
|
// ClassExpression when called
|
||||||
// "const x = new (class {})()",
|
"const x = new (class {})()",
|
||||||
// // ClassProperty
|
// ClassProperty
|
||||||
// "class x {y}",
|
"class x {y}",
|
||||||
// "class x {y = 1}",
|
"class x {y = 1}",
|
||||||
// "class x {y = ext()}",
|
"class x {y = ext()}",
|
||||||
// // ConditionalExpression
|
// // ConditionalExpression
|
||||||
// "const x = ext ? 1 : 2",
|
// "const x = ext ? 1 : 2",
|
||||||
// "const x = true ? 1 : ext()",
|
// "const x = true ? 1 : ext()",
|
||||||
|
|
@ -411,32 +411,32 @@ fn test() {
|
||||||
// TODO: check global function `ext` call when called `x()` in no strict mode
|
// TODO: check global function `ext` call when called `x()` in no strict mode
|
||||||
// "var x=()=>{}; try {} catch (error) {var x=ext}; x()",
|
// "var x=()=>{}; try {} catch (error) {var x=ext}; x()",
|
||||||
// // ClassBody
|
// // ClassBody
|
||||||
// "class x {[ext()](){}}",
|
"class x {[ext()](){}}",
|
||||||
// // ClassBody when called
|
// // ClassBody when called
|
||||||
// "class x {constructor(){ext()}}; new x()",
|
"class x {constructor(){ext()}}; new x()",
|
||||||
// "class x {constructor(){ext()}}; const y = new x()",
|
"class x {constructor(){ext()}}; const y = new x()",
|
||||||
// "class x extends 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 {}; const z = new x()",
|
||||||
// "class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; 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 y{}; class x extends y{constructor(){super()}}; const z = new x()",
|
||||||
// // ClassDeclaration
|
// // ClassDeclaration
|
||||||
// "class x extends ext() {}",
|
"class x extends ext() {}",
|
||||||
// "class x {[ext()](){}}",
|
"class x {[ext()](){}}",
|
||||||
// // ClassDeclaration when called
|
// // ClassDeclaration when called
|
||||||
// "class x {constructor(){ext()}}; new x()",
|
"class x {constructor(){ext()}}; new x()",
|
||||||
// "class x {constructor(){ext()}}; const y = new x()",
|
"class x {constructor(){ext()}}; const y = new x()",
|
||||||
// "class x extends ext {}; const y = new x()",
|
"class x extends ext {}; const y = new x()",
|
||||||
// // ClassExpression
|
// // ClassExpression
|
||||||
// "const x = class extends ext() {}",
|
"const x = class extends ext() {}",
|
||||||
// "const x = class {[ext()](){}}",
|
"const x = class {[ext()](){}}",
|
||||||
// // ClassExpression when called
|
// ClassExpression when called
|
||||||
// "new (class {constructor(){ext()}})()",
|
"new (class {constructor(){ext()}})()",
|
||||||
// "const x = new (class {constructor(){ext()}})()",
|
"const x = new (class {constructor(){ext()}})()",
|
||||||
// "const x = new (class extends ext {})()",
|
"const x = new (class extends ext {})()",
|
||||||
// // ClassProperty
|
// ClassProperty
|
||||||
// "class x {[ext()] = 1}",
|
"class x {[ext()] = 1}",
|
||||||
// // ClassProperty when called
|
// ClassProperty when called
|
||||||
// "class x {y = ext()}; new x()",
|
"class x {y = ext()}; new x()",
|
||||||
// // ConditionalExpression
|
// // ConditionalExpression
|
||||||
// "const x = ext() ? 1 : 2",
|
// "const x = ext() ? 1 : 2",
|
||||||
// "const x = ext ? ext() : 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`
|
⚠ 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]
|
╭─[no_side_effects_in_initialization.tsx:1:15]
|
||||||
1 │ const x = new ext()
|
1 │ const x = new ext()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue