mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter/tree-shaking): detect CallExpression in MemberExpression (#2772)
This commit is contained in:
parent
e10ef03aa9
commit
3c9e77d66f
3 changed files with 128 additions and 7 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use oxc_ast::ast::{
|
use oxc_ast::ast::{
|
||||||
ArrayExpressionElement, AssignmentTarget, ComputedMemberExpression, Expression,
|
Argument, ArrayExpressionElement, AssignmentTarget, CallExpression, ComputedMemberExpression,
|
||||||
IdentifierReference, MemberExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget,
|
Expression, IdentifierReference, MemberExpression, PrivateFieldExpression, Program,
|
||||||
Statement, StaticMemberExpression,
|
SimpleAssignmentTarget, Statement, StaticMemberExpression,
|
||||||
};
|
};
|
||||||
use oxc_semantic::SymbolId;
|
use oxc_semantic::SymbolId;
|
||||||
|
use oxc_span::GetSpan;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{ast_util::get_symbol_id_of_variable, utils::Value, LintContext};
|
use crate::{ast_util::get_symbol_id_of_variable, utils::Value, LintContext};
|
||||||
|
|
@ -66,6 +67,9 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::Identifier(ident) => {
|
Self::Identifier(ident) => {
|
||||||
ident.report_effects(options);
|
ident.report_effects(options);
|
||||||
}
|
}
|
||||||
|
Self::CallExpression(call_expr) => {
|
||||||
|
call_expr.report_effects(options);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +82,65 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
match self {
|
||||||
|
Self::CallExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
Self::Identifier(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// which kind of Expression defines `report_effects_when_called` method.
|
||||||
|
fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
|
||||||
|
matches!(
|
||||||
|
expr,
|
||||||
|
Expression::ArrowFunctionExpression(_)
|
||||||
|
| Expression::CallExpression(_)
|
||||||
|
| Expression::ClassExpression(_)
|
||||||
|
| Expression::ConditionalExpression(_)
|
||||||
|
| Expression::FunctionExpression(_)
|
||||||
|
| Expression::Identifier(_)
|
||||||
|
| Expression::MemberExpression(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ListenerMap for CallExpression<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
self.arguments.iter().for_each(|arg| arg.report_effects(options));
|
||||||
|
if defined_custom_report_effects_when_called(&self.callee) {
|
||||||
|
self.callee.report_effects_when_called(options);
|
||||||
|
} else {
|
||||||
|
// TODO: Not work now
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.callee.span()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
if let Expression::Identifier(ident) = &self.callee {
|
||||||
|
if get_symbol_id_of_variable(ident, options.ctx).is_none() {
|
||||||
|
// TODO: Not work now
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallReturnValue(self.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_mutated(&self, options: &NodeListenerOptions) {
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::MutationOfFunctionReturnValue(self.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ListenerMap for Argument<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
match self {
|
||||||
|
Self::Expression(expr) => expr.report_effects(options),
|
||||||
|
Self::SpreadElement(spread) => {
|
||||||
|
spread.argument.report_effects(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for AssignmentTarget<'a> {
|
impl<'a> ListenerMap for AssignmentTarget<'a> {
|
||||||
|
|
@ -120,6 +183,16 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
let ctx = options.ctx;
|
||||||
|
if get_symbol_id_of_variable(self, ctx).is_none() {
|
||||||
|
ctx.diagnostic(NoSideEffectsDiagnostic::CallGlobal(
|
||||||
|
self.name.to_compact_str(),
|
||||||
|
self.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn report_effects_when_mutated(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_mutated(&self, options: &NodeListenerOptions) {
|
||||||
let ctx = options.ctx;
|
let ctx = options.ctx;
|
||||||
if let Some(symbol_id) = get_symbol_id_of_variable(self, ctx) {
|
if let Some(symbol_id) = get_symbol_id_of_variable(self, ctx) {
|
||||||
|
|
@ -143,6 +216,19 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for MemberExpression<'a> {
|
impl<'a> ListenerMap for MemberExpression<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
match self {
|
||||||
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::StaticMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::PrivateFieldExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
||||||
match self {
|
match self {
|
||||||
Self::ComputedMemberExpression(expr) => {
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
|
@ -161,11 +247,24 @@ impl<'a> ListenerMap for MemberExpression<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for ComputedMemberExpression<'a> {}
|
impl<'a> ListenerMap for ComputedMemberExpression<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
self.expression.report_effects(options);
|
||||||
|
self.object.report_effects(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for StaticMemberExpression<'a> {}
|
impl<'a> ListenerMap for StaticMemberExpression<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
self.object.report_effects(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for PrivateFieldExpression<'a> {}
|
impl<'a> ListenerMap for PrivateFieldExpression<'a> {
|
||||||
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
self.object.report_effects(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for ArrayExpressionElement<'a> {
|
impl<'a> ListenerMap for ArrayExpressionElement<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,22 @@ enum NoSideEffectsDiagnostic {
|
||||||
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `{0}`")]
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `{0}`")]
|
||||||
#[diagnostic(severity(warning))]
|
#[diagnostic(severity(warning))]
|
||||||
Mutation(CompactStr, #[label] Span),
|
Mutation(CompactStr, #[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating function return value")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
MutationOfFunctionReturnValue(#[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
Call(#[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling function return value")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
CallReturnValue(#[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `{0}`")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
CallGlobal(CompactStr, #[label] Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/master/src/rules/no-side-effects-in-initialization.ts>
|
/// <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/master/src/rules/no-side-effects-in-initialization.ts>
|
||||||
|
|
@ -353,7 +369,7 @@ fn test() {
|
||||||
"ext = 1",
|
"ext = 1",
|
||||||
"ext += 1",
|
"ext += 1",
|
||||||
"ext.x = 1",
|
"ext.x = 1",
|
||||||
// "const x = {};x[ext()] = 1",
|
"const x = {};x[ext()] = 1",
|
||||||
// "this.x = 1",
|
// "this.x = 1",
|
||||||
// // AssignmentPattern
|
// // AssignmentPattern
|
||||||
// "const {x = ext()} = {}",
|
// "const {x = ext()} = {}",
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,9 @@ expression: no_side_effects_in_initialization
|
||||||
1 │ ext.x = 1
|
1 │ ext.x = 1
|
||||||
· ───
|
· ───
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
|
⚠ 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:16]
|
||||||
|
1 │ const x = {};x[ext()] = 1
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue