feat(rome_js_analyze): `noCommaOperator`
Closes
MODIFY
crates/rome_diagnostics_categories/src/categories.rs
crates/rome_diagnostics_categories/src/categories.rs
@@ -59,6 +59,7 @@ define_dategories! {
"lint/nursery/noRedundantAlt": "https://docs.rome.tools/lint/rules/noRedundantAlt",
"lint/nursery/noRedundantUseStrict": "https://docs.rome.tools/lint/rules/noRedundantUseStrict",
"lint/nursery/noRestrictedGlobals": "https://docs.rome.tools/lint/rules/noRestrictedGlobals",
+ "lint/nursery/noCommaOperator": "https://docs.rome.tools/lint/rules/noCommaOperator",
"lint/nursery/noSetterReturn": "https://docs.rome.tools/lint/rules/noSetterReturn",
"lint/nursery/noStringCaseMismatch": "https://docs.rome.tools/lint/rules/noStringCaseMismatch",
"lint/nursery/noUnsafeFinally": "https://docs.rome.tools/lint/rules/noUnsafeFinally",

MODIFY
crates/rome_js_analyze/src/analyzers/nursery.rs
crates/rome_js_analyze/src/analyzers/nursery.rs
@@ -4,6 +4,7 @@ use rome_analyze::declare_group;
mod no_access_key;
mod no_assign_in_expressions;
mod no_banned_types;
+mod no_comma_operator;
mod no_const_enum;
mod no_constructor_return;
mod no_distracting_elements;
@@ -27,4 +28,4 @@ mod use_default_switch_clause_last;
mod use_enum_initializers;
mod use_exponentiation_operator;
mod use_numeric_literals;
-declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } }
+declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } }

ADD
crates/rome_js_analyze/src/analyzers/nursery/no_comma_operator.rs
+use rome_analyze::context::RuleContext;
+use rome_analyze::{declare_rule, Ast, Rule, RuleDiagnostic};
+use rome_js_syntax::{JsForStatement, JsSequenceExpression};
+use rome_rowan::AstNode;
+
+declare_rule! {
+ /// Disallow comma operator.
+ ///
+ /// The comma operator includes multiple expressions where only one is expected.
+ /// It evaluates every operand from left to right and returns the value of the last operand.
+ /// It frequently obscures side effects, and its use is often an accident.
+ ///
+ /// The use of the comma operator in the initialization and update parts of a `for` is still allowed.
+ ///
+ /// Source: https://eslint.org/docs/latest/rules/no-sequences
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```js,expect_diagnostic
+ /// const foo = doSomething(), 0;
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// for (; doSomething(), !!test; ) {}
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// // Use a semicolon instead.
+ /// let a, b;
+ /// a = 1, b = 2;
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```js
+ /// for(a = 0, b = 0; (a + b) < 10; a++, b += 2) {}
+ /// ```
+ ///
+ pub(crate) NoCommaOperator {
+ version: "12.0.0",
+ name: "noCommaOperator",
+ recommended: true,
+ }
+}
+
+impl Rule for NoCommaOperator {
+ type Query = Ast<JsSequenceExpression>;
+ type State = ();
+ type Signals = Option<Self::State>;
+ type Options = ();
+
+ fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+ let seq = ctx.query();
+ if let Some(for_stmt) = seq.parent::<JsForStatement>() {
+ // Allow comma operator in initializer and update parts of a `for`
+ if for_stmt.test().map(AstNode::into_syntax).as_ref() != Some(seq.syntax()) {
+ return None;
+ }
+ }
+ Some(())
+ }
+
+ fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
+ let seq = ctx.query();
+ Some(
+ RuleDiagnostic::new(
+ rule_category!(),
+ seq.comma_token().ok()?.text_trimmed_range(),
+ "The comma operator is disallowed.",
+ )
+ .note("Its use is often confusing and obscures side effects."),
+ )
+ }
+}

ADD
crates/rome_js_analyze/tests/specs/nursery/noCommaOperator/invalid.jsonc
+[
+ "a = 1, 2",
+ "do {} while (doSomething(), !!test);",
+ "for (; doSomething(), !!test; );",
+ "if (doSomething(), !!test);",
+ "switch (doSomething(), val) {}",
+ "while (doSomething(), !!test);",
+ "with (doSomething(), val) {}",
+ "a => (doSomething(), a)",
+ "(1), 2",
+ "((1)) , (2)",
+ "while((1) , 2);",
+
+ // Do not allow comma operator in parentheses
+ "var foo = (1, 2);",
+ "(0,eval)(\"foo()\");",
+ "foo(a, (b, c), d);",
+ "do {} while ((doSomething(), !!test));",
+ "for (; (doSomething(), !!test); );",
+ "if ((doSomething(), !!test));",
+ "switch ((doSomething(), val)) {}",
+ "while ((doSomething(), !!test));",
+ "with ((doSomething(), val)) {}",
+ "a => ((doSomething(), a))"
+]

ADD
crates/rome_js_analyze/tests/specs/nursery/noCommaOperator/valid.jsonc
+[
+ // Code without comma operator
+ "let arr = [1, 2];",
+ "let obj = {a: 1, b: 2};",
+ "let a = 1, b = 2;",
+ "foo(a, b, d);",
+
+ // Allowed comma operators
+ "for (i = 0, j = 0; test; );",
+ "for (; test; i++, j++);"
+]

ADD
crates/rome_js_analyze/tests/specs/nursery/noCommaOperator/valid.jsonc.snap
+---
+source: crates/rome_js_analyze/tests/spec_tests.rs
+assertion_line: 92
+expression: valid.jsonc
+---
+# Input
+```js
+let arr = [1, 2];
+```
+
+# Input
+```js
+let obj = {a: 1, b: 2};
+```
+
+# Input
+```js
+let a = 1, b = 2;
+```
+
+# Input
+```js
+foo(a, b, d);
+```
+
+# Input
+```js
+for (i = 0, j = 0; test; );
+```
+
+# Input
+```js
+for (; test; i++, j++);
+```
+
+

MODIFY
crates/rome_js_analyze/tests/specs/nursery/useDefaultParameterLast/invalid.js
crates/rome_js_analyze/tests/specs/nursery/useDefaultParameterLast/invalid.js
@@ -4,8 +4,4 @@ export function g(a, b = 0, c) {}
export function g(a, b /* before */ = /* mid */ 0/* after */) {}
-export function g(
- a,
- b /* before */ = /* mid */ 0/* after */,/* after coma */
- c
-) {}
\ No newline at end of file
+export function g(a, b /* before */ = /* mid */ 0 /* after */ /* after comma */, c) {}
\ No newline at end of file

MODIFY
crates/rome_service/src/configuration/linter/rules.rs
crates/rome_service/src/configuration/linter/rules.rs
@@ -729,6 +729,8 @@ struct NurserySchema {
no_assign_in_expressions: Option<RuleConfiguration>,
#[doc = "Disallow certain types."]
no_banned_types: Option<RuleConfiguration>,
+ #[doc = "Disallow comma operator."]
+ no_comma_operator: Option<RuleConfiguration>,
#[doc = "Disallow TypeScript const enum"]
no_const_enum: Option<RuleConfiguration>,
#[doc = "Disallow returning a value from a constructor."]
@@ -794,10 +796,11 @@ struct NurserySchema {
}
impl Nursery {
const CATEGORY_NAME: &'static str = "nursery";
- pub(crate) const CATEGORY_RULES: [&'static str; 34] = [
+ pub(crate) const CATEGORY_RULES: [&'static str; 35] = [
"noAccessKey",
"noAssignInExpressions",
"noBannedTypes",
+ "noCommaOperator",
"noConstEnum",
"noConstructorReturn",
"noDistractingElements",
@@ -830,9 +833,10 @@ impl Nursery {
"useHookAtTopLevel",
"useNumericLiterals",
];
- const RECOMMENDED_RULES: [&'static str; 25] = [
+ const RECOMMENDED_RULES: [&'static str; 26] = [
"noAssignInExpressions",
"noBannedTypes",
+ "noCommaOperator",
"noConstEnum",
"noConstructorReturn",
"noDistractingElements",
@@ -857,7 +861,7 @@ impl Nursery {
"useExhaustiveDependencies",
"useNumericLiterals",
];
- const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 25] = [
+ const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 26] = [
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[3]),
@@ -868,21 +872,22 @@ impl Nursery {
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[8]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[9]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[10]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[13]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[16]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[11]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[14]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[17]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[18]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[19]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[20]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[21]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[22]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[24]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[26]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[23]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[25]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[27]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[28]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[29]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[30]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[33]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[31]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[34]),
];
pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) }
pub(crate) fn get_enabled_rules(&self) -> IndexSet<RuleFilter> {
@@ -909,7 +914,7 @@ impl Nursery {
pub(crate) fn is_recommended_rule(rule_name: &str) -> bool {
Self::RECOMMENDED_RULES.contains(&rule_name)
}
- pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 25] {
+ pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 26] {
Self::RECOMMENDED_RULES_AS_FILTERS
}
}

MODIFY
editors/vscode/configuration_schema.json
editors/vscode/configuration_schema.json
}
]
},
+ "noCommaOperator": {
+ "description": "Disallow comma operator.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noConstEnum": {
"description": "Disallow TypeScript const enum",
"anyOf": [

MODIFY
npm/backend-jsonrpc/src/workspace.ts
npm/backend-jsonrpc/src/workspace.ts
@@ -297,6 +297,10 @@ export interface Nursery {
* Disallow certain types.
*/
noBannedTypes?: RuleConfiguration;
+ /**
+ * Disallow comma operator.
+ */
+ noCommaOperator?: RuleConfiguration;
/**
* Disallow TypeScript const enum
*/
@@ -711,6 +715,7 @@ export type Category =
| "lint/nursery/noRedundantAlt"
| "lint/nursery/noRedundantUseStrict"
| "lint/nursery/noRestrictedGlobals"
+ | "lint/nursery/noCommaOperator"
| "lint/nursery/noSetterReturn"
| "lint/nursery/noStringCaseMismatch"
| "lint/nursery/noUnsafeFinally"

MODIFY
npm/rome/configuration_schema.json
npm/rome/configuration_schema.json
}
]
},
+ "noCommaOperator": {
+ "description": "Disallow comma operator.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noConstEnum": {
"description": "Disallow TypeScript const enum",
"anyOf": [

MODIFY
website/src/pages/lint/rules/index.mdx
website/src/pages/lint/rules/index.mdx
@@ -492,6 +492,12 @@ Disallow assignments in expressions.
Disallow certain types.
</section>
<section class="rule">
+<h3 data-toc-exclude id="noCommaOperator">
+ <a href="/lint/rules/noCommaOperator">noCommaOperator</a>
+</h3>
+Disallow comma operator.
+</section>
+<section class="rule">
<h3 data-toc-exclude id="noConstEnum">
<a href="/lint/rules/noConstEnum">noConstEnum</a>
</h3>

feat(rome_js_analyze): `noCommaOperator` (#4019)

Co-authored-by: Micha Reiser <micha@rome.tools>
Closes https://github.com/rome/tools/issues/3932