diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 265e217d3..eb6596ea8 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -156,6 +156,7 @@ mod unicorn { pub mod no_object_as_default_parameter; pub mod no_static_only_class; pub mod no_thenable; + pub mod no_this_assignment; pub mod no_unnecessary_await; pub mod no_useless_fallback_in_spread; pub mod prefer_add_event_listener; @@ -298,6 +299,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_object_as_default_parameter, unicorn::no_static_only_class, unicorn::no_thenable, + unicorn::no_this_assignment, unicorn::no_unnecessary_await, unicorn::no_useless_fallback_in_spread, unicorn::prefer_add_event_listener, diff --git a/crates/oxc_linter/src/rules/unicorn/no_this_assignment.rs b/crates/oxc_linter/src/rules/unicorn/no_this_assignment.rs new file mode 100644 index 000000000..1f5edb5dd --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/no_this_assignment.rs @@ -0,0 +1,147 @@ +use oxc_ast::{ + ast::{AssignmentTarget, BindingPatternKind, Expression, SimpleAssignmentTarget}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::{self, Error}, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(no-this-assignment): Do not assign `this` to `{1}`")] +#[diagnostic( + severity(warning), + help("Reference `this` directly instead of assigning it to a variable.") +)] +struct NoThisAssignmentDiagnostic(#[label] pub Span, Atom); + +#[derive(Debug, Default, Clone)] +pub struct NoThisAssignment; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow assigning `this` to a variable. + /// + /// ### Why is this bad? + /// + /// Assigning `this` to a variable is unnecessary and confusing. + /// + /// ### Example + /// ```javascript + /// // fail + /// const foo = this; + /// class Bar { + /// method() { + /// foo.baz(); + /// } + /// } + /// + /// new Bar().method(); + /// + /// // pass + /// class Bar { + /// constructor(fooInstance) { + /// this.fooInstance = fooInstance; + /// } + /// method() { + /// this.fooInstance.baz(); + /// } + /// } + /// + /// new Bar(this).method(); + /// ``` + NoThisAssignment, + pedantic +); + +impl Rule for NoThisAssignment { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::VariableDeclarator(variable_decl) => { + let Some(init) = &variable_decl.init else { + return; + }; + + if !matches!(init.without_parenthesized(), Expression::ThisExpression(_)) { + return; + } + + let BindingPatternKind::BindingIdentifier(binding_ident) = &variable_decl.id.kind + else { + return; + }; + + ctx.diagnostic(NoThisAssignmentDiagnostic( + variable_decl.span, + binding_ident.name.clone(), + )); + } + AstKind::AssignmentExpression(assignment_expr) => { + if !matches!( + assignment_expr.right.without_parenthesized(), + Expression::ThisExpression(_) + ) { + return; + } + + let AssignmentTarget::SimpleAssignmentTarget(simple_assignment_target) = + &assignment_expr.left + else { + return; + }; + + let SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) = + simple_assignment_target + else { + return; + }; + + ctx.diagnostic(NoThisAssignmentDiagnostic( + assignment_expr.span, + ident.name.clone(), + )); + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"const {property} = this;"#, + r#"const property = this.property;"#, + r#"const [element] = this;"#, + r#"const element = this[0];"#, + r#"([element] = this);"#, + r#"element = this[0];"#, + r#"property = this.property;"#, + r#"const [element] = [this];"#, + r#"([element] = [this]);"#, + r#"const {property} = {property: this};"#, + r#"({property} = {property: this});"#, + r#"const self = true && this;"#, + r#"const self = false || this;"#, + r#"const self = false ?? this;"#, + r#"foo.bar = this;"#, + r#"function foo(a = this) {}"#, + r#"function foo({a = this}) {}"#, + r#"function foo([a = this]) {}"#, + ]; + + let fail = vec![ + r#"const foo = this;"#, + r#"let foo;foo = this;"#, + r#"var foo = bar, baz = this;"#, + r#"var foo = (bar), baz = (this);"#, + ]; + + Tester::new_without_config(NoThisAssignment::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_this_assignment.snap b/crates/oxc_linter/src/snapshots/no_this_assignment.snap new file mode 100644 index 000000000..d07bb8693 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_this_assignment.snap @@ -0,0 +1,33 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_this_assignment +--- + ⚠ eslint-plugin-unicorn(no-this-assignment): Do not assign `this` to `foo` + ╭─[no_this_assignment.tsx:1:1] + 1 │ const foo = this; + · ────────── + ╰──── + help: Reference `this` directly instead of assigning it to a variable. + + ⚠ eslint-plugin-unicorn(no-this-assignment): Do not assign `this` to `foo` + ╭─[no_this_assignment.tsx:1:1] + 1 │ let foo;foo = this; + · ────────── + ╰──── + help: Reference `this` directly instead of assigning it to a variable. + + ⚠ eslint-plugin-unicorn(no-this-assignment): Do not assign `this` to `baz` + ╭─[no_this_assignment.tsx:1:1] + 1 │ var foo = bar, baz = this; + · ────────── + ╰──── + help: Reference `this` directly instead of assigning it to a variable. + + ⚠ eslint-plugin-unicorn(no-this-assignment): Do not assign `this` to `baz` + ╭─[no_this_assignment.tsx:1:1] + 1 │ var foo = (bar), baz = (this); + · ──────────── + ╰──── + help: Reference `this` directly instead of assigning it to a variable. + +