feat(rome_js_analyze): new rule noCondAssign
MODIFY
crates/rome_diagnostics_categories/src/categories.rs
crates/rome_diagnostics_categories/src/categories.rs
@@ -76,6 +76,7 @@ define_dategories! {
// nursery
"lint/nursery/noBannedTypes":"https://docs.rome.tools/lint/rules/noBannedTypes",
+ "lint/nursery/noConditionalAssignment": "https://docs.rome.tools/lint/rules/noConditionalAssignment",
"lint/nursery/noConstAssign": "https://docs.rome.tools/lint/rules/noConstAssign",
"lint/nursery/noDupeKeys":"https://docs.rome.tools/lint/rules/noDupeKeys",
"lint/nursery/noExplicitAny": "https://docs.rome.tools/lint/rules/noExplicitAny",

MODIFY
crates/rome_js_analyze/src/analyzers/nursery.rs
crates/rome_js_analyze/src/analyzers/nursery.rs
use rome_analyze::declare_group;
mod no_banned_types;
+mod no_conditional_assignment;
mod no_dupe_keys;
mod no_explicit_any;
mod no_invalid_constructor_super;
mod use_flat_map;
mod use_numeric_literals;
mod use_valid_for_direction;
-declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_dupe_keys :: NoDupeKeys , self :: no_explicit_any :: NoExplicitAny , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } }
+declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_conditional_assignment :: NoConditionalAssignment , self :: no_dupe_keys :: NoDupeKeys , self :: no_explicit_any :: NoExplicitAny , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } }

ADD
crates/rome_js_analyze/src/analyzers/nursery/no_conditional_assignment.rs
+use rome_analyze::context::RuleContext;
+use rome_analyze::{declare_rule, Ast, Rule, RuleDiagnostic};
+use rome_console::markup;
+use rome_js_syntax::*;
+use rome_rowan::{declare_node_union, AstNode};
+
+declare_rule! {
+ /// Disallow assignment operators in conditional expressions.
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```js,expect_diagnostic
+ /// var x;
+ /// if (x = 0) {
+ /// var b = 1;
+ /// }
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// function setHeight(someNode) {
+ /// "use strict";
+ /// do {
+ /// someNode.height = "100px";
+ /// } while (someNode = someNode.parentNode);
+ /// }
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```js
+ /// var x;
+ /// if (x === 0) {
+ /// var b = 1;
+ /// }
+ /// ```
+ ///
+ /// ```js
+ /// function setHeight(someNode) {
+ /// "use strict";
+ /// do {
+ /// someNode.height = "100px";
+ /// } while ((someNode = someNode.parentNode) !== null);
+ /// }
+ /// ```
+ ///
+ /// ```
+ pub(crate) NoConditionalAssignment {
+ version: "11.0.0",
+ name: "noConditionalAssignment",
+ recommended: false,
+ }
+}
+
+declare_node_union! {
+ pub(crate) ConditionalStatement = JsConditionalExpression | JsWhileStatement | JsDoWhileStatement | JsIfStatement | JsForStatement
+}
+
+impl Rule for NoConditionalAssignment {
+ type Query = Ast<ConditionalStatement>;
+ type State = JsAssignmentExpression;
+ type Signals = Option<Self::State>;
+ type Options = ();
+
+ fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+ let stmt = ctx.query();
+ stmt.test()
+ .map(|it| it.omit_parentheses())
+ .and_then(into_js_assignment)
+ }
+
+ fn diagnostic(_ctx: &RuleContext<Self>, expr: &Self::State) -> Option<RuleDiagnostic> {
+ Some(RuleDiagnostic::new(
+ rule_category!(),
+ expr.syntax().text_trimmed_range(),
+ markup! {
+ "Expected a conditional expression and instead saw an assignment."
+ },
+ ))
+ }
+}
+
+fn into_js_assignment(expr: JsAnyExpression) -> Option<JsAssignmentExpression> {
+ if let JsAnyExpression::JsAssignmentExpression(e) = expr {
+ Some(e)
+ } else {
+ None
+ }
+}
+
+impl ConditionalStatement {
+ fn test(&self) -> Option<JsAnyExpression> {
+ match self {
+ Self::JsConditionalExpression(it) => it.test().ok(),
+ Self::JsWhileStatement(it) => it.test().ok(),
+ Self::JsDoWhileStatement(it) => it.test().ok(),
+ Self::JsIfStatement(it) => it.test().ok(),
+ Self::JsForStatement(it) => it.test(),
+ }
+ }
+}

ADD
crates/rome_js_analyze/tests/specs/nursery/noConditionalAssignment/invalid.js
+var x; if (x = 0) { var b = 1; }
+var x; while (x = 0) { var b = 1; }
+var x = 0, y; do { y = x; } while (x = x + 1);
+var x; for(; x+=1 ;){};
+var x; if ((x) = (0));
+if (someNode || (someNode = parentNode)) { }
+while (someNode || (someNode = parentNode)) { }
+do { } while (someNode || (someNode = parentNode));
+for (; (typeof l === 'undefined' ? (l = 0) : l); i++) { }
+if (x = 0) { }
+while (x = 0) { }
+do { } while (x = x + 1);
+for(; x = y; ) { }
+if ((x = 0)) { }
+while ((x = 0)) { }
+do { } while ((x = x + 1));
+for(; (x = y); ) { }
+var x; var b = (x = 0) ? 1 : 0;
+var x; var b = x && (y = 0) ? 1 : 0;
+(((3496.29)).bkufyydt = 2e308) ? foo : bar;
+
\ No newline at end of file

ADD
crates/rome_js_analyze/tests/specs/nursery/noConditionalAssignment/valid.js
+var x = 0; if (x == 0) { var b = 1; }
+var x = 0; if (x == 0) { var b = 1; }
+var x = 5; while (x < 5) { x = x + 1; }
+if ((someNode = someNode.parentNode) !== null) { }
+if ((someNode = someNode.parentNode) !== null) { }
+if ((a = b));
+while ((a = b));
+do {} while ((a = b));
+for (;(a = b););
+for (;;) {}
+if (someNode || (someNode = parentNode)) { }
+while (someNode || (someNode = parentNode)) { }
+do { } while (someNode || (someNode = parentNode));
+for (;someNode || (someNode = parentNode););
+if ((function(node) { return node = parentNode; })(someNode)) { }
+if ((function(node) { return node = parentNode; })(someNode)) { }
+if ((node => node = parentNode)(someNode)) { }
+if ((node => node = parentNode)(someNode)) { }
+if (function(node) { return node = parentNode; }) { }
+if (function(node) { return node = parentNode; }) { }
+x = 0;
+var x; var b = (x === 0) ? 1 : 0;
+switch (foo) { case a = b: bar(); }
+switch (foo) { case a = b: bar(); }
+switch (foo) { case baz + (a = b): bar(); }

MODIFY
crates/rome_service/src/configuration/linter/rules.rs
crates/rome_service/src/configuration/linter/rules.rs
@@ -688,6 +688,7 @@ pub struct Nursery {
#[doc = r" A list of rules that belong to this group"]
struct NurserySchema {
no_banned_types: Option<RuleConfiguration>,
+ no_conditional_assignment: Option<RuleConfiguration>,
no_const_assign: Option<RuleConfiguration>,
no_dupe_keys: Option<RuleConfiguration>,
no_explicit_any: Option<RuleConfiguration>,
@@ -701,8 +702,9 @@ struct NurserySchema {
}
impl Nursery {
const CATEGORY_NAME: &'static str = "nursery";
- pub(crate) const CATEGORY_RULES: [&'static str; 11] = [
+ pub(crate) const CATEGORY_RULES: [&'static str; 12] = [
"noBannedTypes",
+ "noConditionalAssignment",
"noConstAssign",
"noDupeKeys",
"noExplicitAny",

MODIFY
editors/vscode/configuration_schema.json
editors/vscode/configuration_schema.json
}
]
},
+ "noConditionalAssignment": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noConstAssign": {
"anyOf": [
{

MODIFY
npm/backend-jsonrpc/src/workspace.ts
npm/backend-jsonrpc/src/workspace.ts
@@ -218,6 +218,7 @@ export interface Correctness {
*/
export interface Nursery {
noBannedTypes?: RuleConfiguration;
+ noConditionalAssignment?: RuleConfiguration;
noConstAssign?: RuleConfiguration;
noDupeKeys?: RuleConfiguration;
noExplicitAny?: RuleConfiguration;
@@ -398,6 +399,7 @@ export type Category =
| "lint/security/noDangerouslySetInnerHtml"
| "lint/security/noDangerouslySetInnerHtmlWithChildren"
| "lint/nursery/noBannedTypes"
+ | "lint/nursery/noConditionalAssignment"
| "lint/nursery/noConstAssign"
| "lint/nursery/noDupeKeys"
| "lint/nursery/noExplicitAny"

MODIFY
website/src/pages/lint/rules/index.mdx
website/src/pages/lint/rules/index.mdx
@@ -448,6 +448,12 @@ Rules that belong to this group <strong>are not subject to semantic version</str
Disallow certain types.
</section>
<section class="rule">
+<h3 data-toc-exclude id="noConditionalAssignment">
+ <a href="/lint/rules/noConditionalAssignment">noConditionalAssignment</a>
+</h3>
+Disallow assignment operators in conditional expressions.
+</section>
+<section class="rule">
<h3 data-toc-exclude id="noConstAssign">
<a href="/lint/rules/noConstAssign">noConstAssign</a>
</h3>

feat(rome_js_analyze): new rule noCondAssign (#3750)