mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter/tree_shaking): check assignment of identifier (#2697)
This commit is contained in:
parent
f3eab76410
commit
11219d4415
3 changed files with 137 additions and 20 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
· ───
|
||||
╰────
|
||||
|
|
|
|||
Loading…
Reference in a new issue