mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 21:29:01 +00:00
feat(linter/tree-shaking): check CallExpression when called (#2809)
This commit is contained in:
parent
fe12617315
commit
fa39fa87b3
4 changed files with 98 additions and 16 deletions
|
|
@ -1,18 +1,24 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use oxc_ast::ast::{
|
use oxc_ast::{
|
||||||
Argument, ArrayExpressionElement, AssignmentTarget, CallExpression, ComputedMemberExpression,
|
ast::{
|
||||||
Expression, IdentifierReference, MemberExpression, PrivateFieldExpression, Program,
|
Argument, ArrayExpressionElement, AssignmentTarget, CallExpression,
|
||||||
SimpleAssignmentTarget, Statement, StaticMemberExpression,
|
ComputedMemberExpression, Expression, IdentifierReference, MemberExpression,
|
||||||
|
PrivateFieldExpression, Program, SimpleAssignmentTarget, Statement, StaticMemberExpression,
|
||||||
|
},
|
||||||
|
AstKind,
|
||||||
};
|
};
|
||||||
use oxc_semantic::SymbolId;
|
use oxc_semantic::{AstNode, SymbolId};
|
||||||
use oxc_span::GetSpan;
|
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_declaration_of_variable, get_symbol_id_of_variable},
|
||||||
|
utils::{get_write_expr, Value},
|
||||||
|
LintContext,
|
||||||
|
};
|
||||||
|
|
||||||
use super::NoSideEffectsDiagnostic;
|
use super::NoSideEffectsDiagnostic;
|
||||||
|
|
||||||
pub struct NodeListenerOptions<'a, 'b> {
|
pub struct NodeListenerOptions<'a, 'b> {
|
||||||
checked_mutated_nodes: RefCell<FxHashSet<SymbolId>>,
|
checked_mutated_nodes: RefCell<FxHashSet<SymbolId>>,
|
||||||
ctx: &'b LintContext<'a>,
|
ctx: &'b LintContext<'a>,
|
||||||
|
|
@ -54,6 +60,23 @@ impl<'a> ListenerMap for Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we don't need implement all AstNode
|
||||||
|
// it's same as `reportSideEffectsInDefinitionWhenCalled` in eslint-plugin-tree-shaking
|
||||||
|
// <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/463fa1f0bef7caa2b231a38b9c3557051f506c92/src/rules/no-side-effects-in-initialization.ts#L1070-L1080>
|
||||||
|
impl<'a> ListenerMap for AstNode<'a> {
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match self.kind() {
|
||||||
|
AstKind::VariableDeclarator(decl) => {
|
||||||
|
if let Some(init) = &decl.init {
|
||||||
|
init.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for Expression<'a> {
|
impl<'a> ListenerMap for Expression<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -79,7 +102,10 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::Identifier(ident) => {
|
Self::Identifier(ident) => {
|
||||||
ident.report_effects_when_mutated(options);
|
ident.report_effects_when_mutated(options);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {
|
||||||
|
// Default behavior
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::Mutation(self.span()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
|
@ -90,7 +116,10 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::Identifier(expr) => {
|
Self::Identifier(expr) => {
|
||||||
expr.report_effects_when_called(options);
|
expr.report_effects_when_called(options);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {
|
||||||
|
// Default behavior
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,9 +149,18 @@ impl<'a> ListenerMap for CallExpression<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
let ctx = options.ctx;
|
||||||
if let Expression::Identifier(ident) = &self.callee {
|
if let Expression::Identifier(ident) = &self.callee {
|
||||||
if get_symbol_id_of_variable(ident, options.ctx).is_none() {
|
if let Some(node) = get_declaration_of_variable(ident, ctx) {
|
||||||
// TODO: Not work now
|
let Some(parent) = ctx.nodes().parent_kind(node.id()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// TODO: `isLocalVariableAWhitelistedModule`
|
||||||
|
if matches!(parent, AstKind::ImportDeclaration(_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallReturnValue(self.span));
|
||||||
|
} else {
|
||||||
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallReturnValue(self.span));
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallReturnValue(self.span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +223,20 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
|
||||||
|
|
||||||
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
let ctx = options.ctx;
|
let ctx = options.ctx;
|
||||||
if get_symbol_id_of_variable(self, ctx).is_none() {
|
if let Some(symbol_id) = get_symbol_id_of_variable(self, ctx) {
|
||||||
|
let symbol_table = ctx.semantic().symbols();
|
||||||
|
for reference in symbol_table.get_resolved_references(symbol_id) {
|
||||||
|
if reference.is_write() {
|
||||||
|
let node_id = reference.node_id();
|
||||||
|
if let Some(expr) = get_write_expr(node_id, ctx) {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let symbol_table = ctx.semantic().symbols();
|
||||||
|
let node = ctx.nodes().get_node(symbol_table.get_declaration(symbol_id));
|
||||||
|
node.report_effects_when_called(options);
|
||||||
|
} else {
|
||||||
ctx.diagnostic(NoSideEffectsDiagnostic::CallGlobal(
|
ctx.diagnostic(NoSideEffectsDiagnostic::CallGlobal(
|
||||||
self.name.to_compact_str(),
|
self.name.to_compact_str(),
|
||||||
self.span,
|
self.span,
|
||||||
|
|
@ -199,7 +250,12 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
|
||||||
if options.insert_mutated_node(symbol_id) {
|
if options.insert_mutated_node(symbol_id) {
|
||||||
for reference in ctx.symbols().get_resolved_references(symbol_id) {
|
for reference in ctx.symbols().get_resolved_references(symbol_id) {
|
||||||
if reference.is_write() {
|
if reference.is_write() {
|
||||||
ctx.diagnostic(NoSideEffectsDiagnostic::Mutation(
|
let node_id = reference.node_id();
|
||||||
|
if let Some(expr) = get_write_expr(node_id, ctx) {
|
||||||
|
expr.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.diagnostic(NoSideEffectsDiagnostic::MutationWithName(
|
||||||
self.name.to_compact_str(),
|
self.name.to_compact_str(),
|
||||||
self.span,
|
self.span,
|
||||||
));
|
));
|
||||||
|
|
@ -207,7 +263,7 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.diagnostic(NoSideEffectsDiagnostic::Mutation(
|
ctx.diagnostic(NoSideEffectsDiagnostic::MutationWithName(
|
||||||
self.name.to_compact_str(),
|
self.name.to_compact_str(),
|
||||||
self.span,
|
self.span,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,13 @@ enum NoSideEffectsDiagnostic {
|
||||||
#[diagnostic(severity(warning))]
|
#[diagnostic(severity(warning))]
|
||||||
Assignment(CompactStr, #[label] Span),
|
Assignment(CompactStr, #[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
Mutation(#[label] Span),
|
||||||
|
|
||||||
#[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),
|
MutationWithName(CompactStr, #[label] Span),
|
||||||
|
|
||||||
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating function return value")]
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating function return value")]
|
||||||
#[diagnostic(severity(warning))]
|
#[diagnostic(severity(warning))]
|
||||||
|
|
@ -387,7 +391,7 @@ fn test() {
|
||||||
// "(()=>{})(ext(), 1)",
|
// "(()=>{})(ext(), 1)",
|
||||||
// "(()=>{})(1, ext())",
|
// "(()=>{})(1, ext())",
|
||||||
// // CallExpression when called
|
// // CallExpression when called
|
||||||
// "const x = ()=>ext; const y = x(); y()",
|
"const x = ()=>ext; const y = x(); y()",
|
||||||
// // CallExpression when mutated
|
// // CallExpression when mutated
|
||||||
// "const x = ()=>ext; const y = x(); y.z = 1",
|
// "const x = ()=>ext; const y = x(); y.z = 1",
|
||||||
// // CatchClause
|
// // CatchClause
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,9 @@ expression: no_side_effects_in_initialization
|
||||||
1 │ const x = {};x[ext()] = 1
|
1 │ const x = {};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:30]
|
||||||
|
1 │ const x = ()=>ext; const y = x(); y()
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,21 @@
|
||||||
|
use oxc_ast::{ast::Expression, AstKind};
|
||||||
|
use oxc_semantic::AstNodeId;
|
||||||
|
|
||||||
|
use crate::LintContext;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Number(f64),
|
Number(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_write_expr<'a, 'b>(
|
||||||
|
node_id: AstNodeId,
|
||||||
|
ctx: &'b LintContext<'a>,
|
||||||
|
) -> Option<&'b Expression<'a>> {
|
||||||
|
let parent = ctx.nodes().parent_kind(node_id)?;
|
||||||
|
match parent {
|
||||||
|
AstKind::AssignmentExpression(assign_expr) => Some(&assign_expr.right),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue