feat(linter/tree_shaking): check assignment of identifier (#2697)

This commit is contained in:
Wang Wenzhe 2024-03-13 11:45:42 +08:00 committed by GitHub
parent f3eab76410
commit 11219d4415
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 137 additions and 20 deletions

View file

@ -1,6 +1,12 @@
use oxc_ast::ast::{ArrayExpressionElement, Expression, Program, Statement};
use oxc_ast::ast::{
ArrayExpressionElement, AssignmentTarget, ComputedMemberExpression, Expression,
IdentifierReference, MemberExpression, PrivateFieldExpression, Program, SimpleAssignmentTarget,
Statement, StaticMemberExpression,
};
use crate::{utils::Value, LintContext};
use crate::{ast_util::get_declaration_of_variable, utils::Value, LintContext};
use super::NoSideEffectsDiagnostic;
pub trait ListenerMap {
fn report_effects(&self, _ctx: &LintContext) {}
@ -28,16 +34,107 @@ impl<'a> ListenerMap for Statement<'a> {
impl<'a> ListenerMap for Expression<'a> {
fn report_effects(&self, ctx: &LintContext) {
#[allow(clippy::single_match)]
match self {
Self::ArrayExpression(array_expr) => {
array_expr.elements.iter().for_each(|el| el.report_effects(ctx));
}
Self::AssignmentExpression(assign_expr) => {
assign_expr.left.report_effects_when_assigned(ctx);
assign_expr.right.report_effects(ctx);
}
Self::Identifier(ident) => {
ident.report_effects(ctx);
}
_ => {}
}
}
fn report_effects_when_mutated(&self, ctx: &LintContext) {
#[allow(clippy::single_match)]
match self {
Self::Identifier(ident) => {
ident.report_effects_when_mutated(ctx);
}
_ => {}
}
}
}
impl<'a> ListenerMap for AssignmentTarget<'a> {
fn report_effects_when_assigned(&self, ctx: &LintContext) {
match self {
Self::SimpleAssignmentTarget(target) => {
target.report_effects_when_assigned(ctx);
}
Self::AssignmentTargetPattern(_pattern) => {}
}
}
}
impl<'a> ListenerMap for SimpleAssignmentTarget<'a> {
fn report_effects_when_assigned(&self, ctx: &LintContext) {
match self {
Self::AssignmentTargetIdentifier(ident) => {
ident.report_effects_when_assigned(ctx);
}
Self::MemberAssignmentTarget(member) => {
member.report_effects_when_assigned(ctx);
}
_ => {
// For remain TypeScript AST, just visit its expression
if let Some(expr) = self.get_expression() {
expr.report_effects_when_assigned(ctx);
}
}
}
}
}
impl<'a> ListenerMap for IdentifierReference<'a> {
fn report_effects_when_assigned(&self, ctx: &LintContext) {
if get_declaration_of_variable(self, ctx).is_none() {
ctx.diagnostic(NoSideEffectsDiagnostic::Assignment(
self.name.to_compact_str(),
self.span,
));
}
}
fn report_effects_when_mutated(&self, ctx: &LintContext) {
// TODO: check mutation of local variable.
if get_declaration_of_variable(self, ctx).is_none() {
ctx.diagnostic(NoSideEffectsDiagnostic::Mutation(
self.name.to_compact_str(),
self.span,
));
}
}
}
impl<'a> ListenerMap for MemberExpression<'a> {
fn report_effects_when_assigned(&self, ctx: &LintContext) {
match self {
Self::ComputedMemberExpression(expr) => {
expr.report_effects(ctx);
expr.object.report_effects_when_mutated(ctx);
}
Self::StaticMemberExpression(expr) => {
expr.report_effects(ctx);
expr.object.report_effects_when_mutated(ctx);
}
Self::PrivateFieldExpression(expr) => {
expr.report_effects(ctx);
expr.object.report_effects_when_mutated(ctx);
}
}
}
}
impl<'a> ListenerMap for ComputedMemberExpression<'a> {}
impl<'a> ListenerMap for StaticMemberExpression<'a> {}
impl<'a> ListenerMap for PrivateFieldExpression<'a> {}
impl<'a> ListenerMap for ArrayExpressionElement<'a> {
fn report_effects(&self, ctx: &LintContext) {
match self {

View file

@ -1,10 +1,10 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
thiserror::{self, Error},
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_span::{CompactStr, Span};
use crate::{context::LintContext, rule::Rule};
@ -13,11 +13,15 @@ use self::listener_map::ListenerMap;
mod listener_map;
#[derive(Debug, Error, Diagnostic)]
#[error(
"eslint-plugin-tree-shaking(no-side-effects-in-initialization): cannot determine side-effects"
)]
#[diagnostic(severity(warning), help(""))]
struct NoSideEffectsInInitializationDiagnostic(#[label] pub Span);
enum NoSideEffectsDiagnostic {
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of assignment to `{0}`")]
#[diagnostic(severity(warning))]
Assignment(CompactStr, #[label] Span),
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `{0}`")]
#[diagnostic(severity(warning))]
Mutation(CompactStr, #[label] Span),
}
/// <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/master/src/rules/no-side-effects-in-initialization.ts>
#[derive(Debug, Default, Clone)]
@ -76,13 +80,13 @@ fn test() {
// // ArrowFunctionExpression when mutated
// "const x = ()=>{}; x.y = 1",
// // AssignmentExpression
// "var x;x = {}",
// "var x;x += 1",
// "const x = {}; x.y = 1",
// r#"const x = {}; x["y"] = 1"#,
// "function x(){this.y = 1}; const z = new x()",
// "let x = 1; x = 2 + 3",
// "let x; x = 2 + 3",
"var x;x = {}",
"var x;x += 1",
"const x = {}; x.y = 1",
r#"const x = {}; x["y"] = 1"#,
"function x(){this.y = 1}; const z = new x()",
"let x = 1; x = 2 + 3",
"let x; x = 2 + 3",
// // AssignmentPattern
// "const {x = ext} = {}",
// "const {x: y = ext} = {}",
@ -346,9 +350,9 @@ fn test() {
// "((...a)=>{a.x = 1})(ext)",
// "(({a})=>{a.x = 1})(ext)",
// // AssignmentExpression
// "ext = 1",
// "ext += 1",
// "ext.x = 1",
"ext = 1",
"ext += 1",
"ext.x = 1",
// "const x = {};x[ext()] = 1",
// "this.x = 1",
// // AssignmentPattern

View file

@ -2,4 +2,20 @@
source: crates/oxc_linter/src/tester.rs
expression: no_side_effects_in_initialization
---
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of assignment to `ext`
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ ext = 1
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of assignment to `ext`
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ ext += 1
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `ext`
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ ext.x = 1
· ───
╰────