feat(rome_js_analyze): noCatchAssign
MODIFY
crates/rome_js_analyze/src/semantic_analyzers/js.rs
crates/rome_js_analyze/src/semantic_analyzers/js.rs
use rome_analyze::declare_group;
mod no_arguments;
+mod no_catch_assign;
mod no_label_var;
-declare_group! { pub (crate) Js { name : "js" , rules : [no_arguments :: NoArguments , no_label_var :: NoLabelVar ,] } }
+declare_group! { pub (crate) Js { name : "js" , rules : [no_arguments :: NoArguments , no_catch_assign :: NoCatchAssign , no_label_var :: NoLabelVar ,] } }

ADD
crates/rome_js_analyze/src/semantic_analyzers/js/no_catch_assign.rs
+use crate::{semantic_services::Semantic, JsRuleAction};
+use rome_analyze::{context::RuleContext, declare_rule, Rule, RuleCategory, RuleDiagnostic};
+use rome_console::markup;
+use rome_js_syntax::{JsCatchClause, JsIdentifierAssignment, JsSyntaxNode};
+use rome_rowan::AstNode;
+
+declare_rule! {
+ /// Disallow reassigning exceptions in catch clauses
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```js,expect_diagnostic
+ /// try {
+ ///
+ /// } catch (e) {
+ /// e;
+ /// e = 10;
+ /// }
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```js
+ /// try {
+ ///
+ /// } catch (e) {
+ /// let e = 10;
+ /// e = 100;
+ /// }
+ /// ```
+ pub(crate) NoCatchAssign = "noCatchAssign"
+}
+
+impl Rule for NoCatchAssign {
+ const CATEGORY: RuleCategory = RuleCategory::Lint;
+
+ /// Why use [JsCatchClause] instead of [JsIdentifierAssignment] ? Because this could reduce search range.
+ /// We only compare the declaration of [JsCatchClause] with all descent [JsIdentifierAssignment] of its body.
+ type Query = Semantic<JsCatchClause>;
+ /// The first element of `State` is the reassignment of catch parameter, the second element of `State` is the declaration of catch clause.
+ type State = (JsIdentifierAssignment, JsSyntaxNode);
+ type Signals = Vec<Self::State>;
+
+ fn run(ctx: &RuleContext<Self>) -> Vec<Self::State> {
+ let catch_clause = ctx.query();
+ let model = ctx.model();
+
+ catch_clause
+ .declaration()
+ .and_then(|decl| {
+ // catch_binding
+ // ## Example
+ // try {
+
+ // } catch (catch_binding) {
+ // ^^^^^^^^^^^^^
+ // }
+ let catch_binding = decl.binding().ok()?;
+ let catch_binding_syntax = catch_binding.syntax();
+ let body = catch_clause.body().ok()?;
+ let mut invalid_assign = vec![];
+
+ for assignment in body
+ .syntax()
+ .descendants()
+ .filter_map(JsIdentifierAssignment::cast)
+ {
+ let decl_binding = model.declaration(&assignment).unwrap();
+ if decl_binding.syntax() == catch_binding_syntax {
+ invalid_assign.push((assignment, catch_binding_syntax.clone()));
+ }
+ }
+ Some(invalid_assign)
+ })
+ .unwrap_or_default()
+ }
+
+ fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
+ let (assignment, catch_binding_syntax) = state;
+ let diagnostic = RuleDiagnostic::warning(
+ assignment.syntax().text_trimmed_range(),
+ markup! {
+ " Do not "<Emphasis>"reassign catch parameters."</Emphasis>""
+ },
+ )
+ .secondary(
+ catch_binding_syntax.text_trimmed_range(),
+ markup! {
+ "The catch parameter is declared here"
+ },
+ );
+
+ Some(diagnostic.footer_note("Use a local variable instead."))
+ }
+
+ fn action(_: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
+ None
+ }
+}

ADD
crates/rome_js_analyze/tests/specs/js/noCatchAssign.js
+// invalid
+try { } catch (e) { e; e = 10; }
+try {
+
+} catch (error) {
+ error = 100;
+ {
+ error = 10;
+ }
+}
\ No newline at end of file

MODIFY
website/src/docs/lint/rules/index.md
website/src/docs/lint/rules/index.md
@@ -28,6 +28,13 @@ Disallow the use of <code>arguments</code>
Disallows using an async function as a Promise executor.
</div>
<div class="rule">
+<h3 data-toc-exclude id="noCatchAssign">
+ <a href="/docs/lint/rules/noCatchAssign">noCatchAssign</a>
+ <a class="header-anchor" href="#noCatchAssign"></a>
+</h3>
+Disallow reassigning exceptions in catch clauses
+</div>
+<div class="rule">
<h3 data-toc-exclude id="noCompareNegZero">
<a href="/docs/lint/rules/noCompareNegZero">noCompareNegZero</a>
<a class="header-anchor" href="#noCompareNegZero"></a>

feat(rome_js_analyze): noCatchAssign (#2849)