mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +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 oxc_ast::ast::{
|
||||
ArrayExpressionElement, AssignmentTarget, ComputedMemberExpression, Expression,
|
||||
IdentifierReference, MemberExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget,
|
||||
Statement, StaticMemberExpression,
|
||||
Argument, ArrayExpressionElement, AssignmentTarget, CallExpression, ComputedMemberExpression,
|
||||
Expression, IdentifierReference, MemberExpression, PrivateFieldExpression, Program,
|
||||
SimpleAssignmentTarget, Statement, StaticMemberExpression,
|
||||
};
|
||||
use oxc_semantic::SymbolId;
|
||||
use oxc_span::GetSpan;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
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) => {
|
||||
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> {
|
||||
|
|
@ -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) {
|
||||
let ctx = options.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> {
|
||||
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) {
|
||||
match self {
|
||||
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> {
|
||||
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}`")]
|
||||
#[diagnostic(severity(warning))]
|
||||
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>
|
||||
|
|
@ -353,7 +369,7 @@ fn test() {
|
|||
"ext = 1",
|
||||
"ext += 1",
|
||||
"ext.x = 1",
|
||||
// "const x = {};x[ext()] = 1",
|
||||
"const x = {};x[ext()] = 1",
|
||||
// "this.x = 1",
|
||||
// // AssignmentPattern
|
||||
// "const {x = ext()} = {}",
|
||||
|
|
|
|||
|
|
@ -19,3 +19,9 @@ expression: no_side_effects_in_initialization
|
|||
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