From c75e616546017b502e45909a4b7f0e57d87c7cda Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:49:42 +0200 Subject: [PATCH] feat(rome_js_analyze): noUnsafeOptionalChain, no-unsafe-optional-chaining #3985 --- .../src/categories.rs | 1 + .../rome_js_analyze/src/analyzers/nursery.rs | 3 +- .../nursery/no_unsafe_optional_chaining.rs | 380 ++++ .../noUnsafeOptionalChaining/invalid.cjs | 1 + .../noUnsafeOptionalChaining/invalid.cjs.snap | 30 + .../noUnsafeOptionalChaining/invalid.js | 81 + .../noUnsafeOptionalChaining/invalid.js.snap | 1598 +++++++++++++++++ .../nursery/noUnsafeOptionalChaining/valid.js | 87 + .../noUnsafeOptionalChaining/valid.js.snap | 97 + .../validArithmetic.js | 92 + .../validArithmetic.js.snap | 102 ++ .../src/configuration/linter/rules.rs | 123 +- .../src/configuration/parse/json/rules.rs | 19 + editors/vscode/configuration_schema.json | 7 + npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 7 + website/src/pages/lint/rules/index.mdx | 6 + .../lint/rules/noUnsafeOptionalChaining.md | 172 ++ 18 files changed, 2757 insertions(+), 54 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/nursery/no_unsafe_optional_chaining.rs create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js.snap create mode 100644 website/src/pages/lint/rules/noUnsafeOptionalChaining.md diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 699cf435ce0..aa9fc176812 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -98,6 +98,7 @@ define_dategories! { "lint/nursery/noUselessRename": "https://docs.rome.tools/lint/rules/noUselessRename", "lint/nursery/useValidForDirection": "https://docs.rome.tools/lint/rules/useValidForDirection", "lint/nursery/useHookAtTopLevel": "https://docs.rome.tools/lint/rules/useHookAtTopLevel", + "lint/nursery/noUnsafeOptionalChaining": "https://docs.rome.tools/lint/rules/noUnsafeOptionalChaining", "lint/nursery/noDuplicateJsxProps": "https://docs.rome.tools/lint/rules/noDuplicateJsxProps", "lint/nursery/noDuplicateClassMembers": "https://docs.rome.tools/lint/rules/noDuplicateClassMembers", "lint/nursery/useYield": "https://docs.rome.tools/lint/rules/useYield", diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index c15a7654723..76fce3e3d5c 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -35,6 +35,7 @@ mod no_string_case_mismatch; mod no_switch_declarations; mod no_unreachable_super; mod no_unsafe_finally; +mod no_unsafe_optional_chaining; mod no_unused_labels; mod no_useless_rename; mod no_useless_switch_case; @@ -48,4 +49,4 @@ mod use_is_nan; mod use_media_caption; mod use_numeric_literals; mod use_yield; -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_confusing_labels :: NoConfusingLabels , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_class_members :: NoDuplicateClassMembers , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_labels :: NoExtraLabels , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_global_object_calls :: NoGlobalObjectCalls , self :: no_header_scope :: NoHeaderScope , self :: no_inferrable_types :: NoInferrableTypes , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_parameter_properties :: NoParameterProperties , self :: no_precision_loss :: NoPrecisionLoss , self :: no_prototype_builtins :: NoPrototypeBuiltins , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_assignment :: NoSelfAssignment , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_switch_declarations :: NoSwitchDeclarations , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_unused_labels :: NoUnusedLabels , self :: no_useless_rename :: NoUselessRename , 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_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals , self :: use_yield :: UseYield ,] } } +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_confusing_labels :: NoConfusingLabels , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_class_members :: NoDuplicateClassMembers , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_labels :: NoExtraLabels , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_global_object_calls :: NoGlobalObjectCalls , self :: no_header_scope :: NoHeaderScope , self :: no_inferrable_types :: NoInferrableTypes , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_parameter_properties :: NoParameterProperties , self :: no_precision_loss :: NoPrecisionLoss , self :: no_prototype_builtins :: NoPrototypeBuiltins , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_assignment :: NoSelfAssignment , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_switch_declarations :: NoSwitchDeclarations , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_unsafe_optional_chaining :: NoUnsafeOptionalChaining , self :: no_unused_labels :: NoUnusedLabels , self :: no_useless_rename :: NoUselessRename , 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_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals , self :: use_yield :: UseYield ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_unsafe_optional_chaining.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_unsafe_optional_chaining.rs new file mode 100644 index 00000000000..cba235b56dd --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_unsafe_optional_chaining.rs @@ -0,0 +1,380 @@ +use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_js_syntax::{ + AnyJsAssignmentPattern, AnyJsBindingPattern, AnyJsExpression, JsAssignmentExpression, + JsAssignmentWithDefault, JsAwaitExpression, JsCallExpression, JsComputedMemberExpression, + JsConditionalExpression, JsExtendsClause, JsForOfStatement, JsInExpression, + JsInitializerClause, JsInstanceofExpression, JsLogicalExpression, JsLogicalOperator, + JsNewExpression, JsObjectAssignmentPatternProperty, JsObjectMemberList, + JsParenthesizedExpression, JsSequenceExpression, JsSpread, JsStaticMemberExpression, + JsTemplateExpression, JsVariableDeclarator, JsWithStatement, +}; +use rome_rowan::{declare_node_union, AstNode, TextRange}; + +declare_rule! { + /// Disallow the use of optional chaining in contexts where the undefined value is not allowed. + /// + /// The optional chaining (?.) expression can short-circuit with a return value of undefined. + /// Therefore, treating an evaluated optional chaining expression as a function, object, number, etc., can cause TypeError or unexpected results. + /// Also, parentheses limit the scope of short-circuiting in chains. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// 1 in obj?.foo; + /// ``` + /// + /// ```cjs,expect_diagnostic + /// with (obj?.foo); + /// ``` + /// + /// ```js,expect_diagnostic + /// for (bar of obj?.foo); + /// ``` + /// + /// ```js,expect_diagnostic + /// bar instanceof obj?.foo; + /// ``` + /// + /// ```js,expect_diagnostic + /// const { bar } = obj?.foo; + /// ``` + /// + /// ```js,expect_diagnostic + /// (obj?.foo)(); + /// ``` + /// + /// ```js,expect_diagnostic + /// (baz?.bar).foo; + /// ``` + /// + /// ## Valid + /// + /// ```js + /// (obj?.foo)?.(); + /// obj?.foo(); + /// (obj?.foo ?? bar)(); + /// obj?.foo.bar; + /// obj.foo?.bar; + /// foo?.()?.bar; + /// ``` + /// + pub(crate) NoUnsafeOptionalChaining { + version: "12.0.0", + name: "noUnsafeOptionalChaining", + recommended: true, + } +} + +impl Rule for NoUnsafeOptionalChaining { + type Query = Ast; + type State = TextRange; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let query_node = ctx.query(); + + // need to check only optional chain nodes + if !query_node.is_optional() { + return None; + } + + let mut node: RuleNode = query_node.clone().into(); + let mut parent = node.parent::(); + // parentheses limit the scope of short-circuiting in chains + // (a?.b).c // here we have an error + // a?.b.c // ok + let mut is_inside_parenthesis = false; + + while let Some(current_parent) = parent.take() { + match ¤t_parent { + RuleNode::JsParenthesizedExpression(expression) => { + // parentheses limit the scope of short-circuiting in chains + is_inside_parenthesis = true; + parent = expression.parent::() + } + RuleNode::JsAwaitExpression(expression) => parent = expression.parent::(), + RuleNode::JsExtendsClause(extends) => { + // class A extends obj?.foo {} + return Some(extends.syntax().text_trimmed_range()); + } + RuleNode::JsNewExpression(expression) => { + // If we're here, it means we've found a error + // new a?.b + // new (a?.b)() + return Some(expression.syntax().text_trimmed_range()); + } + RuleNode::JsLogicalExpression(expression) => { + match expression.operator().ok()? { + JsLogicalOperator::NullishCoalescing | JsLogicalOperator::LogicalOr => { + // for logical or and nullish we need to check only the right expression + // (a?.b || a?.b).c() + if expression.right().ok()?.syntax() == node.syntax() { + parent = expression.parent::() + } + } + // for logical and we need check both branches + // (a?.b && a?.b).c() + JsLogicalOperator::LogicalAnd => parent = expression.parent::(), + } + } + RuleNode::JsSequenceExpression(expression) => { + let is_last_in_sequence = expression.parent::().is_none(); + + // need to check only the rightmost expression in the sequence + // a, b, c?.() + if is_last_in_sequence && expression.right().ok()?.syntax() == node.syntax() { + parent = expression.parent::() + } + } + RuleNode::JsConditionalExpression(expression) => { + // need to check consequent and alternate branches + // (a ? obj?.foo : obj?.foo)(); + // but not test expression + // (obj?.foo ? a : b)(); + if node.syntax() == expression.consequent().ok()?.syntax() + || node.syntax() == expression.alternate().ok()?.syntax() + { + parent = expression.parent::() + } + } + RuleNode::JsCallExpression(expression) => { + if expression.is_optional() { + // The current optional chain is inside another optional chain which will also be processed by the rule so we can skip current optional chain + // a?.b?.() + return None; + } + + if is_inside_parenthesis { + // it means we've found a error because parentheses limit the scope + // (a?.b)() + return Some(expression.arguments().ok()?.syntax().text_trimmed_range()); + } + + // a()... + parent = expression.parent::() + } + RuleNode::JsStaticMemberExpression(expression) => { + if expression.is_optional() { + // The current optional chain is inside another optional chain which will also be processed by the rule so we can skip current optional chain + // a?.b?.c + return None; + } + + if is_inside_parenthesis { + // it means we've found a error because parentheses limit the scope + // (a?.b).c + return Some(expression.member().ok()?.syntax().text_trimmed_range()); + } + + // a.b.... + parent = expression.parent::() + } + RuleNode::JsComputedMemberExpression(expression) => { + if expression.is_optional() { + // The current optional chain is inside another optional chain which will also be processed by the rule so we can skip current optional chain + // a?.[b]?.[c] + return None; + } + + if is_inside_parenthesis { + // it means we've found a error because parentheses limit the scope + // (a?.[b]).c + return Some(TextRange::new( + expression + .l_brack_token() + .ok()? + .text_trimmed_range() + .start(), + expression.r_brack_token().ok()?.text_trimmed_range().end(), + )); + } + + // a[b]... + parent = expression.parent::() + } + RuleNode::JsTemplateExpression(expression) => { + // a?.b`` + // (a?.b)`` + return Some(TextRange::new( + expression.l_tick_token().ok()?.text_trimmed_range().start(), + expression.r_tick_token().ok()?.text_trimmed_range().end(), + )); + } + RuleNode::JsForOfStatement(statement) => { + if node.syntax() == statement.expression().ok()?.syntax() { + // we can have an error only if we have an optional chain in the expression node + // for (foo of obj?.bar); + return Some(statement.syntax().text_trimmed_range()); + } + } + RuleNode::JsWithStatement(statement) => { + if node.syntax() == statement.object().ok()?.syntax() { + // we can have an error only if we have an optional chain in the object part + // with (obj?.foo) {}; + return Some(statement.syntax().text_trimmed_range()); + } + } + RuleNode::JsInitializerClause(initializer) => { + if let Some(parent) = initializer.parent::() { + if matches!( + parent.id(), + Ok(AnyJsBindingPattern::JsObjectBindingPattern(_) + | AnyJsBindingPattern::JsArrayBindingPattern(_),) + ) { + return Some(parent.syntax().text_trimmed_range()); + } + } else if let Some(parent) = + initializer.parent::() + { + if matches!( + parent.pattern(), + Ok(AnyJsAssignmentPattern::JsObjectAssignmentPattern(_) + | AnyJsAssignmentPattern::JsArrayAssignmentPattern(_),) + ) { + // ({bar: [ foo ] = obj?.prop} = {}); + return Some(parent.syntax().text_trimmed_range()); + } + } + } + RuleNode::JsAssignmentExpression(expression) => { + if matches!( + expression.left(), + Ok(AnyJsAssignmentPattern::JsObjectAssignmentPattern(_) + | AnyJsAssignmentPattern::JsArrayAssignmentPattern(_),) + ) { + return Some(expression.syntax().text_trimmed_range()); + } + } + RuleNode::JsAssignmentWithDefault(assigment) => { + if matches!( + assigment.pattern(), + Ok(AnyJsAssignmentPattern::JsObjectAssignmentPattern(_) + | AnyJsAssignmentPattern::JsArrayAssignmentPattern(_)) + ) { + // [{ foo } = obj?.bar] = []; + return Some(assigment.syntax().text_trimmed_range()); + } + } + RuleNode::JsSpread(spread) => { + // it's not an error to have a spread inside object + // { ...a?.b } + if spread.parent::().is_none() { + return Some(spread.syntax().text_trimmed_range()); + } + } + RuleNode::JsInExpression(expression) => { + if node.syntax() == expression.object().ok()?.syntax() { + // we can have an error only if we have an optional chain in the object part + // a in foo?.bar; + return Some(expression.syntax().text_trimmed_range()); + } + } + RuleNode::JsInstanceofExpression(expression) => { + if node.syntax() == expression.right().ok()?.syntax() { + // we can have an error only if we have an optional chain in the right part + // foo instanceof obj?.prop; + return Some(expression.syntax().text_trimmed_range()); + } + } + }; + + node = current_parent; + } + + None + } + + fn diagnostic(ctx: &RuleContext, range: &Self::State) -> Option { + let query_node = ctx.query(); + + Some( + RuleDiagnostic::new( + rule_category!(), + query_node.range(), + markup! { + "Unsafe usage of optional chaining." + }, + ) + .detail( + range, + "If it short-circuits with 'undefined' the evaluation will throw TypeError here:", + ), + ) + } +} + +declare_node_union! { + pub(crate) QueryNode = JsCallExpression | JsStaticMemberExpression | JsComputedMemberExpression +} + +impl QueryNode { + pub fn is_optional(&self) -> bool { + match self { + QueryNode::JsCallExpression(expression) => expression.is_optional(), + QueryNode::JsStaticMemberExpression(expression) => expression.is_optional(), + QueryNode::JsComputedMemberExpression(expression) => expression.is_optional(), + } + } + + pub fn range(&self) -> Option { + let token = match self { + QueryNode::JsCallExpression(expression) => expression.optional_chain_token(), + QueryNode::JsStaticMemberExpression(expression) => expression.operator_token().ok(), + QueryNode::JsComputedMemberExpression(expression) => expression.optional_chain_token(), + }; + + Some(token?.text_trimmed_range()) + } +} + +impl From for AnyJsExpression { + fn from(node: QueryNode) -> AnyJsExpression { + match node { + QueryNode::JsCallExpression(expression) => expression.into(), + QueryNode::JsStaticMemberExpression(expression) => expression.into(), + QueryNode::JsComputedMemberExpression(expression) => expression.into(), + } + } +} + +impl From for RuleNode { + fn from(node: QueryNode) -> RuleNode { + match node { + QueryNode::JsCallExpression(expression) => RuleNode::JsCallExpression(expression), + QueryNode::JsStaticMemberExpression(expression) => { + RuleNode::JsStaticMemberExpression(expression) + } + QueryNode::JsComputedMemberExpression(expression) => { + RuleNode::JsComputedMemberExpression(expression) + } + } + } +} + +declare_node_union! { + /// Only these variants of the union can be part of an unsafe optional chain. + pub(crate) RuleNode = + JsLogicalExpression + | JsSequenceExpression + | JsConditionalExpression + | JsAwaitExpression + | JsParenthesizedExpression + | JsCallExpression + | JsNewExpression + | JsStaticMemberExpression + | JsComputedMemberExpression + | JsTemplateExpression + | JsForOfStatement + | JsWithStatement + | JsInitializerClause + | JsAssignmentExpression + | JsSpread + | JsExtendsClause + | JsInExpression + | JsInstanceofExpression + | JsAssignmentWithDefault +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs new file mode 100644 index 00000000000..2d4c42cd02d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs @@ -0,0 +1 @@ +with (obj?.foo) {}; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs.snap b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs.snap new file mode 100644 index 00000000000..14a35560de4 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.cjs.snap @@ -0,0 +1,30 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.cjs +--- +# Input +```js +with (obj?.foo) {}; + +``` + +# Diagnostics +``` +invalid.cjs:1:10 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + > 1 │ with (obj?.foo) {}; + │ ^^ + 2 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + > 1 │ with (obj?.foo) {}; + │ ^^^^^^^^^^^^^^^^^^ + 2 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js new file mode 100644 index 00000000000..7201d852ea7 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js @@ -0,0 +1,81 @@ +(obj?.foo)(); +(obj.foo ?? bar?.baz)(); +(obj.foo || bar?.baz)(); +(obj?.foo && bar)(); +(bar && obj?.foo)(); +(obj?.foo?.())(); +(obj?.foo).bar; +(obj?.foo)[1]; +(obj?.foo)`template` +new (obj?.foo)(); +new (obj?.foo?.())() +new (obj?.foo?.() || obj?.bar)() +async function foo() { (await obj?.foo)(); } +async function foo() { (await obj?.foo).bar; } +async function foo() { (bar?.baz ?? await obj?.foo)(); } +async function foo() { (bar && await obj?.foo)(); } +async function foo() { (await (bar && obj?.foo))(); } + +// spread +[...obj?.foo]; +bar(...obj?.foo); +new Bar(...obj?.foo); + +// destructuring +const {foo} = obj?.bar; +const {foo} = obj?.bar(); +const {foo: bar} = obj?.bar(); +const [foo] = obj?.bar; +const [foo] = obj?.bar || obj?.foo; +([foo] = obj?.bar); +const [foo] = obj?.bar?.(); +[{ foo } = obj?.bar] = []; +({bar: [ foo ] = obj?.prop} = {}); +[[ foo ] = obj?.bar] = []; +async function foo() { const {foo} = await obj?.bar; } +async function foo() { const {foo} = await obj?.bar(); } +async function foo() { const [foo] = await obj?.bar || await obj?.foo; } +async function foo() { ([foo] = await obj?.bar); } + +// class declaration +class A extends obj?.foo {} +async function foo() { class A extends (await obj?.foo) {}} + +// class expression +var a = class A extends obj?.foo {} +async function foo() { var a = class A extends (await obj?.foo) {}} + +// relational operations +foo instanceof obj?.prop +async function foo() { foo instanceof await obj?.prop } +1 in foo?.bar; +async function foo() { 1 in await foo?.bar; } + +// for...of +for (foo of obj?.bar); +async function foo() { for (foo of await obj?.bar);} + +// sequence expression +(foo, obj?.foo)(); +(foo, obj?.foo)[1]; +async function foo() { (await (foo, obj?.foo))(); } +async function foo() { ((foo, await obj?.foo))(); } +async function foo() { (foo, await obj?.foo)[1]; } +async function foo() { (await (foo, obj?.foo)) [1]; } + +// conditional expression +(a ? obj?.foo : b)(); +(a ? b : obj?.foo)(); +(a ? obj?.foo : b)[1]; +(a ? b : obj?.foo).bar; +async function foo() { (await (a ? obj?.foo : b))(); } +async function foo() { (a ? await obj?.foo : b)(); } +async function foo() { (await (a ? b : obj?.foo))(); } +async function foo() { (await (a ? obj?.foo : b))[1]; } +async function foo() { (await (a ? b : obj?.foo)).bar; } +async function foo() { (a ? b : await obj?.foo).bar; } + +(obj?.foo && obj?.baz).bar + +async function foo() { with ( await obj?.foo) {}; } +(foo ? obj?.foo : obj?.bar).bar diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js.snap new file mode 100644 index 00000000000..224582ff060 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/invalid.js.snap @@ -0,0 +1,1598 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +(obj?.foo)(); +(obj.foo ?? bar?.baz)(); +(obj.foo || bar?.baz)(); +(obj?.foo && bar)(); +(bar && obj?.foo)(); +(obj?.foo?.())(); +(obj?.foo).bar; +(obj?.foo)[1]; +(obj?.foo)`template` +new (obj?.foo)(); +new (obj?.foo?.())() +new (obj?.foo?.() || obj?.bar)() +async function foo() { (await obj?.foo)(); } +async function foo() { (await obj?.foo).bar; } +async function foo() { (bar?.baz ?? await obj?.foo)(); } +async function foo() { (bar && await obj?.foo)(); } +async function foo() { (await (bar && obj?.foo))(); } + +// spread +[...obj?.foo]; +bar(...obj?.foo); +new Bar(...obj?.foo); + +// destructuring +const {foo} = obj?.bar; +const {foo} = obj?.bar(); +const {foo: bar} = obj?.bar(); +const [foo] = obj?.bar; +const [foo] = obj?.bar || obj?.foo; +([foo] = obj?.bar); +const [foo] = obj?.bar?.(); +[{ foo } = obj?.bar] = []; +({bar: [ foo ] = obj?.prop} = {}); +[[ foo ] = obj?.bar] = []; +async function foo() { const {foo} = await obj?.bar; } +async function foo() { const {foo} = await obj?.bar(); } +async function foo() { const [foo] = await obj?.bar || await obj?.foo; } +async function foo() { ([foo] = await obj?.bar); } + +// class declaration +class A extends obj?.foo {} +async function foo() { class A extends (await obj?.foo) {}} + +// class expression +var a = class A extends obj?.foo {} +async function foo() { var a = class A extends (await obj?.foo) {}} + +// relational operations +foo instanceof obj?.prop +async function foo() { foo instanceof await obj?.prop } +1 in foo?.bar; +async function foo() { 1 in await foo?.bar; } + +// for...of +for (foo of obj?.bar); +async function foo() { for (foo of await obj?.bar);} + +// sequence expression +(foo, obj?.foo)(); +(foo, obj?.foo)[1]; +async function foo() { (await (foo, obj?.foo))(); } +async function foo() { ((foo, await obj?.foo))(); } +async function foo() { (foo, await obj?.foo)[1]; } +async function foo() { (await (foo, obj?.foo)) [1]; } + +// conditional expression +(a ? obj?.foo : b)(); +(a ? b : obj?.foo)(); +(a ? obj?.foo : b)[1]; +(a ? b : obj?.foo).bar; +async function foo() { (await (a ? obj?.foo : b))(); } +async function foo() { (a ? await obj?.foo : b)(); } +async function foo() { (await (a ? b : obj?.foo))(); } +async function foo() { (await (a ? obj?.foo : b))[1]; } +async function foo() { (await (a ? b : obj?.foo)).bar; } +async function foo() { (a ? b : await obj?.foo).bar; } + +(obj?.foo && obj?.baz).bar + +async function foo() { with ( await obj?.foo) {}; } +(foo ? obj?.foo : obj?.bar).bar + +``` + +# Diagnostics +``` +invalid.js:1:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + > 1 │ (obj?.foo)(); + │ ^^ + 2 │ (obj.foo ?? bar?.baz)(); + 3 │ (obj.foo || bar?.baz)(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + > 1 │ (obj?.foo)(); + │ ^^ + 2 │ (obj.foo ?? bar?.baz)(); + 3 │ (obj.foo || bar?.baz)(); + + +``` + +``` +invalid.js:2:16 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 1 │ (obj?.foo)(); + > 2 │ (obj.foo ?? bar?.baz)(); + │ ^^ + 3 │ (obj.foo || bar?.baz)(); + 4 │ (obj?.foo && bar)(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 1 │ (obj?.foo)(); + > 2 │ (obj.foo ?? bar?.baz)(); + │ ^^ + 3 │ (obj.foo || bar?.baz)(); + 4 │ (obj?.foo && bar)(); + + +``` + +``` +invalid.js:3:16 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 1 │ (obj?.foo)(); + 2 │ (obj.foo ?? bar?.baz)(); + > 3 │ (obj.foo || bar?.baz)(); + │ ^^ + 4 │ (obj?.foo && bar)(); + 5 │ (bar && obj?.foo)(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 1 │ (obj?.foo)(); + 2 │ (obj.foo ?? bar?.baz)(); + > 3 │ (obj.foo || bar?.baz)(); + │ ^^ + 4 │ (obj?.foo && bar)(); + 5 │ (bar && obj?.foo)(); + + +``` + +``` +invalid.js:4:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 2 │ (obj.foo ?? bar?.baz)(); + 3 │ (obj.foo || bar?.baz)(); + > 4 │ (obj?.foo && bar)(); + │ ^^ + 5 │ (bar && obj?.foo)(); + 6 │ (obj?.foo?.())(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 2 │ (obj.foo ?? bar?.baz)(); + 3 │ (obj.foo || bar?.baz)(); + > 4 │ (obj?.foo && bar)(); + │ ^^ + 5 │ (bar && obj?.foo)(); + 6 │ (obj?.foo?.())(); + + +``` + +``` +invalid.js:5:12 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 3 │ (obj.foo || bar?.baz)(); + 4 │ (obj?.foo && bar)(); + > 5 │ (bar && obj?.foo)(); + │ ^^ + 6 │ (obj?.foo?.())(); + 7 │ (obj?.foo).bar; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 3 │ (obj.foo || bar?.baz)(); + 4 │ (obj?.foo && bar)(); + > 5 │ (bar && obj?.foo)(); + │ ^^ + 6 │ (obj?.foo?.())(); + 7 │ (obj?.foo).bar; + + +``` + +``` +invalid.js:6:10 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 4 │ (obj?.foo && bar)(); + 5 │ (bar && obj?.foo)(); + > 6 │ (obj?.foo?.())(); + │ ^^ + 7 │ (obj?.foo).bar; + 8 │ (obj?.foo)[1]; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 4 │ (obj?.foo && bar)(); + 5 │ (bar && obj?.foo)(); + > 6 │ (obj?.foo?.())(); + │ ^^ + 7 │ (obj?.foo).bar; + 8 │ (obj?.foo)[1]; + + +``` + +``` +invalid.js:7:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 5 │ (bar && obj?.foo)(); + 6 │ (obj?.foo?.())(); + > 7 │ (obj?.foo).bar; + │ ^^ + 8 │ (obj?.foo)[1]; + 9 │ (obj?.foo)`template` + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 5 │ (bar && obj?.foo)(); + 6 │ (obj?.foo?.())(); + > 7 │ (obj?.foo).bar; + │ ^^^ + 8 │ (obj?.foo)[1]; + 9 │ (obj?.foo)`template` + + +``` + +``` +invalid.js:8:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 6 │ (obj?.foo?.())(); + 7 │ (obj?.foo).bar; + > 8 │ (obj?.foo)[1]; + │ ^^ + 9 │ (obj?.foo)`template` + 10 │ new (obj?.foo)(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 6 │ (obj?.foo?.())(); + 7 │ (obj?.foo).bar; + > 8 │ (obj?.foo)[1]; + │ ^^^ + 9 │ (obj?.foo)`template` + 10 │ new (obj?.foo)(); + + +``` + +``` +invalid.js:9:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 7 │ (obj?.foo).bar; + 8 │ (obj?.foo)[1]; + > 9 │ (obj?.foo)`template` + │ ^^ + 10 │ new (obj?.foo)(); + 11 │ new (obj?.foo?.())() + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 7 │ (obj?.foo).bar; + 8 │ (obj?.foo)[1]; + > 9 │ (obj?.foo)`template` + │ ^^^^^^^^^^ + 10 │ new (obj?.foo)(); + 11 │ new (obj?.foo?.())() + + +``` + +``` +invalid.js:10:9 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 8 │ (obj?.foo)[1]; + 9 │ (obj?.foo)`template` + > 10 │ new (obj?.foo)(); + │ ^^ + 11 │ new (obj?.foo?.())() + 12 │ new (obj?.foo?.() || obj?.bar)() + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 8 │ (obj?.foo)[1]; + 9 │ (obj?.foo)`template` + > 10 │ new (obj?.foo)(); + │ ^^^^^^^^^^^^^^^^ + 11 │ new (obj?.foo?.())() + 12 │ new (obj?.foo?.() || obj?.bar)() + + +``` + +``` +invalid.js:11:14 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 9 │ (obj?.foo)`template` + 10 │ new (obj?.foo)(); + > 11 │ new (obj?.foo?.())() + │ ^^ + 12 │ new (obj?.foo?.() || obj?.bar)() + 13 │ async function foo() { (await obj?.foo)(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 9 │ (obj?.foo)`template` + 10 │ new (obj?.foo)(); + > 11 │ new (obj?.foo?.())() + │ ^^^^^^^^^^^^^^^^^^^^ + 12 │ new (obj?.foo?.() || obj?.bar)() + 13 │ async function foo() { (await obj?.foo)(); } + + +``` + +``` +invalid.js:12:25 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 10 │ new (obj?.foo)(); + 11 │ new (obj?.foo?.())() + > 12 │ new (obj?.foo?.() || obj?.bar)() + │ ^^ + 13 │ async function foo() { (await obj?.foo)(); } + 14 │ async function foo() { (await obj?.foo).bar; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 10 │ new (obj?.foo)(); + 11 │ new (obj?.foo?.())() + > 12 │ new (obj?.foo?.() || obj?.bar)() + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ async function foo() { (await obj?.foo)(); } + 14 │ async function foo() { (await obj?.foo).bar; } + + +``` + +``` +invalid.js:13:34 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 11 │ new (obj?.foo?.())() + 12 │ new (obj?.foo?.() || obj?.bar)() + > 13 │ async function foo() { (await obj?.foo)(); } + │ ^^ + 14 │ async function foo() { (await obj?.foo).bar; } + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 11 │ new (obj?.foo?.())() + 12 │ new (obj?.foo?.() || obj?.bar)() + > 13 │ async function foo() { (await obj?.foo)(); } + │ ^^ + 14 │ async function foo() { (await obj?.foo).bar; } + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + + +``` + +``` +invalid.js:14:34 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 12 │ new (obj?.foo?.() || obj?.bar)() + 13 │ async function foo() { (await obj?.foo)(); } + > 14 │ async function foo() { (await obj?.foo).bar; } + │ ^^ + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + 16 │ async function foo() { (bar && await obj?.foo)(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 12 │ new (obj?.foo?.() || obj?.bar)() + 13 │ async function foo() { (await obj?.foo)(); } + > 14 │ async function foo() { (await obj?.foo).bar; } + │ ^^^ + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + 16 │ async function foo() { (bar && await obj?.foo)(); } + + +``` + +``` +invalid.js:15:46 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 13 │ async function foo() { (await obj?.foo)(); } + 14 │ async function foo() { (await obj?.foo).bar; } + > 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + │ ^^ + 16 │ async function foo() { (bar && await obj?.foo)(); } + 17 │ async function foo() { (await (bar && obj?.foo))(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 13 │ async function foo() { (await obj?.foo)(); } + 14 │ async function foo() { (await obj?.foo).bar; } + > 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + │ ^^ + 16 │ async function foo() { (bar && await obj?.foo)(); } + 17 │ async function foo() { (await (bar && obj?.foo))(); } + + +``` + +``` +invalid.js:16:41 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 14 │ async function foo() { (await obj?.foo).bar; } + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + > 16 │ async function foo() { (bar && await obj?.foo)(); } + │ ^^ + 17 │ async function foo() { (await (bar && obj?.foo))(); } + 18 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 14 │ async function foo() { (await obj?.foo).bar; } + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + > 16 │ async function foo() { (bar && await obj?.foo)(); } + │ ^^ + 17 │ async function foo() { (await (bar && obj?.foo))(); } + 18 │ + + +``` + +``` +invalid.js:17:42 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + 16 │ async function foo() { (bar && await obj?.foo)(); } + > 17 │ async function foo() { (await (bar && obj?.foo))(); } + │ ^^ + 18 │ + 19 │ // spread + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 15 │ async function foo() { (bar?.baz ?? await obj?.foo)(); } + 16 │ async function foo() { (bar && await obj?.foo)(); } + > 17 │ async function foo() { (await (bar && obj?.foo))(); } + │ ^^ + 18 │ + 19 │ // spread + + +``` + +``` +invalid.js:20:8 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 19 │ // spread + > 20 │ [...obj?.foo]; + │ ^^ + 21 │ bar(...obj?.foo); + 22 │ new Bar(...obj?.foo); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 19 │ // spread + > 20 │ [...obj?.foo]; + │ ^^^^^^^^^^^ + 21 │ bar(...obj?.foo); + 22 │ new Bar(...obj?.foo); + + +``` + +``` +invalid.js:21:11 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 19 │ // spread + 20 │ [...obj?.foo]; + > 21 │ bar(...obj?.foo); + │ ^^ + 22 │ new Bar(...obj?.foo); + 23 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 19 │ // spread + 20 │ [...obj?.foo]; + > 21 │ bar(...obj?.foo); + │ ^^^^^^^^^^^ + 22 │ new Bar(...obj?.foo); + 23 │ + + +``` + +``` +invalid.js:22:15 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 20 │ [...obj?.foo]; + 21 │ bar(...obj?.foo); + > 22 │ new Bar(...obj?.foo); + │ ^^ + 23 │ + 24 │ // destructuring + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 20 │ [...obj?.foo]; + 21 │ bar(...obj?.foo); + > 22 │ new Bar(...obj?.foo); + │ ^^^^^^^^^^^ + 23 │ + 24 │ // destructuring + + +``` + +``` +invalid.js:25:18 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 24 │ // destructuring + > 25 │ const {foo} = obj?.bar; + │ ^^ + 26 │ const {foo} = obj?.bar(); + 27 │ const {foo: bar} = obj?.bar(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 24 │ // destructuring + > 25 │ const {foo} = obj?.bar; + │ ^^^^^^^^^^^^^^^^ + 26 │ const {foo} = obj?.bar(); + 27 │ const {foo: bar} = obj?.bar(); + + +``` + +``` +invalid.js:26:18 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 24 │ // destructuring + 25 │ const {foo} = obj?.bar; + > 26 │ const {foo} = obj?.bar(); + │ ^^ + 27 │ const {foo: bar} = obj?.bar(); + 28 │ const [foo] = obj?.bar; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 24 │ // destructuring + 25 │ const {foo} = obj?.bar; + > 26 │ const {foo} = obj?.bar(); + │ ^^^^^^^^^^^^^^^^^^ + 27 │ const {foo: bar} = obj?.bar(); + 28 │ const [foo] = obj?.bar; + + +``` + +``` +invalid.js:27:23 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 25 │ const {foo} = obj?.bar; + 26 │ const {foo} = obj?.bar(); + > 27 │ const {foo: bar} = obj?.bar(); + │ ^^ + 28 │ const [foo] = obj?.bar; + 29 │ const [foo] = obj?.bar || obj?.foo; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 25 │ const {foo} = obj?.bar; + 26 │ const {foo} = obj?.bar(); + > 27 │ const {foo: bar} = obj?.bar(); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ const [foo] = obj?.bar; + 29 │ const [foo] = obj?.bar || obj?.foo; + + +``` + +``` +invalid.js:28:18 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 26 │ const {foo} = obj?.bar(); + 27 │ const {foo: bar} = obj?.bar(); + > 28 │ const [foo] = obj?.bar; + │ ^^ + 29 │ const [foo] = obj?.bar || obj?.foo; + 30 │ ([foo] = obj?.bar); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 26 │ const {foo} = obj?.bar(); + 27 │ const {foo: bar} = obj?.bar(); + > 28 │ const [foo] = obj?.bar; + │ ^^^^^^^^^^^^^^^^ + 29 │ const [foo] = obj?.bar || obj?.foo; + 30 │ ([foo] = obj?.bar); + + +``` + +``` +invalid.js:29:30 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 27 │ const {foo: bar} = obj?.bar(); + 28 │ const [foo] = obj?.bar; + > 29 │ const [foo] = obj?.bar || obj?.foo; + │ ^^ + 30 │ ([foo] = obj?.bar); + 31 │ const [foo] = obj?.bar?.(); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 27 │ const {foo: bar} = obj?.bar(); + 28 │ const [foo] = obj?.bar; + > 29 │ const [foo] = obj?.bar || obj?.foo; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 30 │ ([foo] = obj?.bar); + 31 │ const [foo] = obj?.bar?.(); + + +``` + +``` +invalid.js:30:13 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 28 │ const [foo] = obj?.bar; + 29 │ const [foo] = obj?.bar || obj?.foo; + > 30 │ ([foo] = obj?.bar); + │ ^^ + 31 │ const [foo] = obj?.bar?.(); + 32 │ [{ foo } = obj?.bar] = []; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 28 │ const [foo] = obj?.bar; + 29 │ const [foo] = obj?.bar || obj?.foo; + > 30 │ ([foo] = obj?.bar); + │ ^^^^^^^^^^^^^^^^ + 31 │ const [foo] = obj?.bar?.(); + 32 │ [{ foo } = obj?.bar] = []; + + +``` + +``` +invalid.js:31:23 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 29 │ const [foo] = obj?.bar || obj?.foo; + 30 │ ([foo] = obj?.bar); + > 31 │ const [foo] = obj?.bar?.(); + │ ^^ + 32 │ [{ foo } = obj?.bar] = []; + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 29 │ const [foo] = obj?.bar || obj?.foo; + 30 │ ([foo] = obj?.bar); + > 31 │ const [foo] = obj?.bar?.(); + │ ^^^^^^^^^^^^^^^^^^^^ + 32 │ [{ foo } = obj?.bar] = []; + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + + +``` + +``` +invalid.js:32:15 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 30 │ ([foo] = obj?.bar); + 31 │ const [foo] = obj?.bar?.(); + > 32 │ [{ foo } = obj?.bar] = []; + │ ^^ + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + 34 │ [[ foo ] = obj?.bar] = []; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 30 │ ([foo] = obj?.bar); + 31 │ const [foo] = obj?.bar?.(); + > 32 │ [{ foo } = obj?.bar] = []; + │ ^^^^^^^^^^^^^^^^^^ + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + 34 │ [[ foo ] = obj?.bar] = []; + + +``` + +``` +invalid.js:33:21 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 31 │ const [foo] = obj?.bar?.(); + 32 │ [{ foo } = obj?.bar] = []; + > 33 │ ({bar: [ foo ] = obj?.prop} = {}); + │ ^^ + 34 │ [[ foo ] = obj?.bar] = []; + 35 │ async function foo() { const {foo} = await obj?.bar; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 31 │ const [foo] = obj?.bar?.(); + 32 │ [{ foo } = obj?.bar] = []; + > 33 │ ({bar: [ foo ] = obj?.prop} = {}); + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 34 │ [[ foo ] = obj?.bar] = []; + 35 │ async function foo() { const {foo} = await obj?.bar; } + + +``` + +``` +invalid.js:34:15 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 32 │ [{ foo } = obj?.bar] = []; + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + > 34 │ [[ foo ] = obj?.bar] = []; + │ ^^ + 35 │ async function foo() { const {foo} = await obj?.bar; } + 36 │ async function foo() { const {foo} = await obj?.bar(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 32 │ [{ foo } = obj?.bar] = []; + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + > 34 │ [[ foo ] = obj?.bar] = []; + │ ^^^^^^^^^^^^^^^^^^ + 35 │ async function foo() { const {foo} = await obj?.bar; } + 36 │ async function foo() { const {foo} = await obj?.bar(); } + + +``` + +``` +invalid.js:35:47 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + 34 │ [[ foo ] = obj?.bar] = []; + > 35 │ async function foo() { const {foo} = await obj?.bar; } + │ ^^ + 36 │ async function foo() { const {foo} = await obj?.bar(); } + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 33 │ ({bar: [ foo ] = obj?.prop} = {}); + 34 │ [[ foo ] = obj?.bar] = []; + > 35 │ async function foo() { const {foo} = await obj?.bar; } + │ ^^^^^^^^^^^^^^^^^^^^^^ + 36 │ async function foo() { const {foo} = await obj?.bar(); } + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + + +``` + +``` +invalid.js:36:47 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 34 │ [[ foo ] = obj?.bar] = []; + 35 │ async function foo() { const {foo} = await obj?.bar; } + > 36 │ async function foo() { const {foo} = await obj?.bar(); } + │ ^^ + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + 38 │ async function foo() { ([foo] = await obj?.bar); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 34 │ [[ foo ] = obj?.bar] = []; + 35 │ async function foo() { const {foo} = await obj?.bar; } + > 36 │ async function foo() { const {foo} = await obj?.bar(); } + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + 38 │ async function foo() { ([foo] = await obj?.bar); } + + +``` + +``` +invalid.js:37:65 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 35 │ async function foo() { const {foo} = await obj?.bar; } + 36 │ async function foo() { const {foo} = await obj?.bar(); } + > 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + │ ^^ + 38 │ async function foo() { ([foo] = await obj?.bar); } + 39 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 35 │ async function foo() { const {foo} = await obj?.bar; } + 36 │ async function foo() { const {foo} = await obj?.bar(); } + > 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 38 │ async function foo() { ([foo] = await obj?.bar); } + 39 │ + + +``` + +``` +invalid.js:38:42 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 36 │ async function foo() { const {foo} = await obj?.bar(); } + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + > 38 │ async function foo() { ([foo] = await obj?.bar); } + │ ^^ + 39 │ + 40 │ // class declaration + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 36 │ async function foo() { const {foo} = await obj?.bar(); } + 37 │ async function foo() { const [foo] = await obj?.bar || await obj?.foo; } + > 38 │ async function foo() { ([foo] = await obj?.bar); } + │ ^^^^^^^^^^^^^^^^^^^^^^ + 39 │ + 40 │ // class declaration + + +``` + +``` +invalid.js:41:20 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 40 │ // class declaration + > 41 │ class A extends obj?.foo {} + │ ^^ + 42 │ async function foo() { class A extends (await obj?.foo) {}} + 43 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 40 │ // class declaration + > 41 │ class A extends obj?.foo {} + │ ^^^^^^^^^^^^^^^^ + 42 │ async function foo() { class A extends (await obj?.foo) {}} + 43 │ + + +``` + +``` +invalid.js:42:50 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 40 │ // class declaration + 41 │ class A extends obj?.foo {} + > 42 │ async function foo() { class A extends (await obj?.foo) {}} + │ ^^ + 43 │ + 44 │ // class expression + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 40 │ // class declaration + 41 │ class A extends obj?.foo {} + > 42 │ async function foo() { class A extends (await obj?.foo) {}} + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 43 │ + 44 │ // class expression + + +``` + +``` +invalid.js:45:28 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 44 │ // class expression + > 45 │ var a = class A extends obj?.foo {} + │ ^^ + 46 │ async function foo() { var a = class A extends (await obj?.foo) {}} + 47 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 44 │ // class expression + > 45 │ var a = class A extends obj?.foo {} + │ ^^^^^^^^^^^^^^^^ + 46 │ async function foo() { var a = class A extends (await obj?.foo) {}} + 47 │ + + +``` + +``` +invalid.js:46:58 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 44 │ // class expression + 45 │ var a = class A extends obj?.foo {} + > 46 │ async function foo() { var a = class A extends (await obj?.foo) {}} + │ ^^ + 47 │ + 48 │ // relational operations + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 44 │ // class expression + 45 │ var a = class A extends obj?.foo {} + > 46 │ async function foo() { var a = class A extends (await obj?.foo) {}} + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 47 │ + 48 │ // relational operations + + +``` + +``` +invalid.js:49:19 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 48 │ // relational operations + > 49 │ foo instanceof obj?.prop + │ ^^ + 50 │ async function foo() { foo instanceof await obj?.prop } + 51 │ 1 in foo?.bar; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 48 │ // relational operations + > 49 │ foo instanceof obj?.prop + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ async function foo() { foo instanceof await obj?.prop } + 51 │ 1 in foo?.bar; + + +``` + +``` +invalid.js:50:48 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 48 │ // relational operations + 49 │ foo instanceof obj?.prop + > 50 │ async function foo() { foo instanceof await obj?.prop } + │ ^^ + 51 │ 1 in foo?.bar; + 52 │ async function foo() { 1 in await foo?.bar; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 48 │ // relational operations + 49 │ foo instanceof obj?.prop + > 50 │ async function foo() { foo instanceof await obj?.prop } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 51 │ 1 in foo?.bar; + 52 │ async function foo() { 1 in await foo?.bar; } + + +``` + +``` +invalid.js:51:9 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 49 │ foo instanceof obj?.prop + 50 │ async function foo() { foo instanceof await obj?.prop } + > 51 │ 1 in foo?.bar; + │ ^^ + 52 │ async function foo() { 1 in await foo?.bar; } + 53 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 49 │ foo instanceof obj?.prop + 50 │ async function foo() { foo instanceof await obj?.prop } + > 51 │ 1 in foo?.bar; + │ ^^^^^^^^^^^^^ + 52 │ async function foo() { 1 in await foo?.bar; } + 53 │ + + +``` + +``` +invalid.js:52:38 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 50 │ async function foo() { foo instanceof await obj?.prop } + 51 │ 1 in foo?.bar; + > 52 │ async function foo() { 1 in await foo?.bar; } + │ ^^ + 53 │ + 54 │ // for...of + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 50 │ async function foo() { foo instanceof await obj?.prop } + 51 │ 1 in foo?.bar; + > 52 │ async function foo() { 1 in await foo?.bar; } + │ ^^^^^^^^^^^^^^^^^^^ + 53 │ + 54 │ // for...of + + +``` + +``` +invalid.js:55:16 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 54 │ // for...of + > 55 │ for (foo of obj?.bar); + │ ^^ + 56 │ async function foo() { for (foo of await obj?.bar);} + 57 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 54 │ // for...of + > 55 │ for (foo of obj?.bar); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 56 │ async function foo() { for (foo of await obj?.bar);} + 57 │ + + +``` + +``` +invalid.js:56:45 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 54 │ // for...of + 55 │ for (foo of obj?.bar); + > 56 │ async function foo() { for (foo of await obj?.bar);} + │ ^^ + 57 │ + 58 │ // sequence expression + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 54 │ // for...of + 55 │ for (foo of obj?.bar); + > 56 │ async function foo() { for (foo of await obj?.bar);} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 57 │ + 58 │ // sequence expression + + +``` + +``` +invalid.js:59:10 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 58 │ // sequence expression + > 59 │ (foo, obj?.foo)(); + │ ^^ + 60 │ (foo, obj?.foo)[1]; + 61 │ async function foo() { (await (foo, obj?.foo))(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 58 │ // sequence expression + > 59 │ (foo, obj?.foo)(); + │ ^^ + 60 │ (foo, obj?.foo)[1]; + 61 │ async function foo() { (await (foo, obj?.foo))(); } + + +``` + +``` +invalid.js:60:10 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 58 │ // sequence expression + 59 │ (foo, obj?.foo)(); + > 60 │ (foo, obj?.foo)[1]; + │ ^^ + 61 │ async function foo() { (await (foo, obj?.foo))(); } + 62 │ async function foo() { ((foo, await obj?.foo))(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 58 │ // sequence expression + 59 │ (foo, obj?.foo)(); + > 60 │ (foo, obj?.foo)[1]; + │ ^^^ + 61 │ async function foo() { (await (foo, obj?.foo))(); } + 62 │ async function foo() { ((foo, await obj?.foo))(); } + + +``` + +``` +invalid.js:61:41 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 59 │ (foo, obj?.foo)(); + 60 │ (foo, obj?.foo)[1]; + > 61 │ async function foo() { (await (foo, obj?.foo))(); } + │ ^^ + 62 │ async function foo() { ((foo, await obj?.foo))(); } + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 59 │ (foo, obj?.foo)(); + 60 │ (foo, obj?.foo)[1]; + > 61 │ async function foo() { (await (foo, obj?.foo))(); } + │ ^^ + 62 │ async function foo() { ((foo, await obj?.foo))(); } + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + + +``` + +``` +invalid.js:62:40 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 60 │ (foo, obj?.foo)[1]; + 61 │ async function foo() { (await (foo, obj?.foo))(); } + > 62 │ async function foo() { ((foo, await obj?.foo))(); } + │ ^^ + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 60 │ (foo, obj?.foo)[1]; + 61 │ async function foo() { (await (foo, obj?.foo))(); } + > 62 │ async function foo() { ((foo, await obj?.foo))(); } + │ ^^ + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + + +``` + +``` +invalid.js:63:39 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 61 │ async function foo() { (await (foo, obj?.foo))(); } + 62 │ async function foo() { ((foo, await obj?.foo))(); } + > 63 │ async function foo() { (foo, await obj?.foo)[1]; } + │ ^^ + 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + 65 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 61 │ async function foo() { (await (foo, obj?.foo))(); } + 62 │ async function foo() { ((foo, await obj?.foo))(); } + > 63 │ async function foo() { (foo, await obj?.foo)[1]; } + │ ^^^ + 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + 65 │ + + +``` + +``` +invalid.js:64:40 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 62 │ async function foo() { ((foo, await obj?.foo))(); } + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + > 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + │ ^^ + 65 │ + 66 │ // conditional expression + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 62 │ async function foo() { ((foo, await obj?.foo))(); } + 63 │ async function foo() { (foo, await obj?.foo)[1]; } + > 64 │ async function foo() { (await (foo, obj?.foo)) [1]; } + │ ^^^ + 65 │ + 66 │ // conditional expression + + +``` + +``` +invalid.js:67:9 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 66 │ // conditional expression + > 67 │ (a ? obj?.foo : b)(); + │ ^^ + 68 │ (a ? b : obj?.foo)(); + 69 │ (a ? obj?.foo : b)[1]; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 66 │ // conditional expression + > 67 │ (a ? obj?.foo : b)(); + │ ^^ + 68 │ (a ? b : obj?.foo)(); + 69 │ (a ? obj?.foo : b)[1]; + + +``` + +``` +invalid.js:68:13 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 66 │ // conditional expression + 67 │ (a ? obj?.foo : b)(); + > 68 │ (a ? b : obj?.foo)(); + │ ^^ + 69 │ (a ? obj?.foo : b)[1]; + 70 │ (a ? b : obj?.foo).bar; + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 66 │ // conditional expression + 67 │ (a ? obj?.foo : b)(); + > 68 │ (a ? b : obj?.foo)(); + │ ^^ + 69 │ (a ? obj?.foo : b)[1]; + 70 │ (a ? b : obj?.foo).bar; + + +``` + +``` +invalid.js:69:9 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 67 │ (a ? obj?.foo : b)(); + 68 │ (a ? b : obj?.foo)(); + > 69 │ (a ? obj?.foo : b)[1]; + │ ^^ + 70 │ (a ? b : obj?.foo).bar; + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 67 │ (a ? obj?.foo : b)(); + 68 │ (a ? b : obj?.foo)(); + > 69 │ (a ? obj?.foo : b)[1]; + │ ^^^ + 70 │ (a ? b : obj?.foo).bar; + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + + +``` + +``` +invalid.js:70:13 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 68 │ (a ? b : obj?.foo)(); + 69 │ (a ? obj?.foo : b)[1]; + > 70 │ (a ? b : obj?.foo).bar; + │ ^^ + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 68 │ (a ? b : obj?.foo)(); + 69 │ (a ? obj?.foo : b)[1]; + > 70 │ (a ? b : obj?.foo).bar; + │ ^^^ + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + + +``` + +``` +invalid.js:71:39 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 69 │ (a ? obj?.foo : b)[1]; + 70 │ (a ? b : obj?.foo).bar; + > 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + │ ^^ + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 69 │ (a ? obj?.foo : b)[1]; + 70 │ (a ? b : obj?.foo).bar; + > 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + │ ^^ + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + + +``` + +``` +invalid.js:72:38 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 70 │ (a ? b : obj?.foo).bar; + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + > 72 │ async function foo() { (a ? await obj?.foo : b)(); } + │ ^^ + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 70 │ (a ? b : obj?.foo).bar; + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + > 72 │ async function foo() { (a ? await obj?.foo : b)(); } + │ ^^ + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + + +``` + +``` +invalid.js:73:43 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + > 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + │ ^^ + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 71 │ async function foo() { (await (a ? obj?.foo : b))(); } + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + > 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + │ ^^ + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + + +``` + +``` +invalid.js:74:39 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + > 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + │ ^^ + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 72 │ async function foo() { (a ? await obj?.foo : b)(); } + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + > 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + │ ^^^ + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + + +``` + +``` +invalid.js:75:43 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + > 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + │ ^^ + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 73 │ async function foo() { (await (a ? b : obj?.foo))(); } + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + > 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + │ ^^^ + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + + +``` + +``` +invalid.js:76:42 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + > 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + │ ^^ + 77 │ + 78 │ (obj?.foo && obj?.baz).bar + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 74 │ async function foo() { (await (a ? obj?.foo : b))[1]; } + 75 │ async function foo() { (await (a ? b : obj?.foo)).bar; } + > 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + │ ^^^ + 77 │ + 78 │ (obj?.foo && obj?.baz).bar + + +``` + +``` +invalid.js:78:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + > 78 │ (obj?.foo && obj?.baz).bar + │ ^^ + 79 │ + 80 │ async function foo() { with ( await obj?.foo) {}; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + > 78 │ (obj?.foo && obj?.baz).bar + │ ^^^ + 79 │ + 80 │ async function foo() { with ( await obj?.foo) {}; } + + +``` + +``` +invalid.js:78:17 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + > 78 │ (obj?.foo && obj?.baz).bar + │ ^^ + 79 │ + 80 │ async function foo() { with ( await obj?.foo) {}; } + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 76 │ async function foo() { (a ? b : await obj?.foo).bar; } + 77 │ + > 78 │ (obj?.foo && obj?.baz).bar + │ ^^^ + 79 │ + 80 │ async function foo() { with ( await obj?.foo) {}; } + + +``` + +``` +invalid.js:81:11 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 80 │ async function foo() { with ( await obj?.foo) {}; } + > 81 │ (foo ? obj?.foo : obj?.bar).bar + │ ^^ + 82 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 80 │ async function foo() { with ( await obj?.foo) {}; } + > 81 │ (foo ? obj?.foo : obj?.bar).bar + │ ^^^ + 82 │ + + +``` + +``` +invalid.js:81:22 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unsafe usage of optional chaining. + + 80 │ async function foo() { with ( await obj?.foo) {}; } + > 81 │ (foo ? obj?.foo : obj?.bar).bar + │ ^^ + 82 │ + + i If it short-circuits with 'undefined' the evaluation will throw TypeError here: + + 80 │ async function foo() { with ( await obj?.foo) {}; } + > 81 │ (foo ? obj?.foo : obj?.bar).bar + │ ^^^ + 82 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js new file mode 100644 index 00000000000..cb1d6af8b56 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js @@ -0,0 +1,87 @@ +var foo; +class Foo {} +!!obj?.foo +obj?.foo(); +obj?.foo?.(); +(obj?.foo ?? bar)(); +(obj?.foo)?.() +(obj?.foo ?? bar?.baz)?.() +(obj.foo)?.(); +obj?.foo.bar; +obj?.foo?.bar; +(obj?.foo)?.bar; +(obj?.foo)?.bar.baz; +(obj?.foo)?.().bar +(obj?.foo ?? bar).baz; +(obj?.foo ?? val)`template` +new (obj?.foo ?? val)() +new bar(); +obj?.foo?.()(); +const {foo} = obj?.baz || {}; +const foo = obj?.bar +foo = obj?.bar +foo.bar = obj?.bar +bar(...obj?.foo ?? []); +var bar = {...foo?.bar}; +foo?.bar in {}; +foo?.bar < foo?.baz; +foo?.bar <= foo?.baz; +foo?.bar > foo?.baz; +foo?.bar >= foo?.baz; +[foo = obj?.bar] = []; +[foo.bar = obj?.bar] = []; +({foo = obj?.bar} = obj); +({foo: obj.bar = obj?.baz} = obj); +(foo?.bar, bar)(); +(foo?.bar ? baz : qux)(); + +async function func() { + await obj?.foo(); + await obj?.foo?.(); + (await obj?.foo)?.(); + (await obj?.foo)?.bar; + await bar?.baz; + await (foo ?? obj?.foo.baz); + (await bar?.baz ?? bar).baz; + (await bar?.baz ?? await bar).baz; + await (foo?.bar ? baz : qux); +} + +// logical operations +(obj?.foo ?? bar?.baz ?? qux)(); +((obj?.foo ?? bar?.baz) || qux)(); +((obj?.foo || bar?.baz) || qux)(); +((obj?.foo && bar?.baz) || qux)(); + +// The default value option disallowArithmeticOperators is false +obj?.foo - bar; +obj?.foo + bar; +obj?.foo * bar; +obj?.foo / bar; +obj?.foo % bar; +obj?.foo ** bar; ++obj?.foo; +-obj?.foo; +bar += obj?.foo; +bar -= obj?.foo; +bar %= obj?.foo; +bar **= obj?.foo; +bar *= obj?.boo +bar /= obj?.boo +async function func() { + await obj?.foo + await obj?.bar; + await obj?.foo - await obj?.bar; + await obj?.foo * await obj?.bar; + +await obj?.foo; + -await obj?.foo; + bar += await obj?.foo; + bar -= await obj?.foo; + bar %= await obj?.foo; + bar **= await obj?.foo; + bar *= await obj?.boo; + bar /= await obj?.boo; +} + +obj?.foo - bar; + +for (a in b?.c) {} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js.snap new file mode 100644 index 00000000000..cde332c6959 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/valid.js.snap @@ -0,0 +1,97 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +var foo; +class Foo {} +!!obj?.foo +obj?.foo(); +obj?.foo?.(); +(obj?.foo ?? bar)(); +(obj?.foo)?.() +(obj?.foo ?? bar?.baz)?.() +(obj.foo)?.(); +obj?.foo.bar; +obj?.foo?.bar; +(obj?.foo)?.bar; +(obj?.foo)?.bar.baz; +(obj?.foo)?.().bar +(obj?.foo ?? bar).baz; +(obj?.foo ?? val)`template` +new (obj?.foo ?? val)() +new bar(); +obj?.foo?.()(); +const {foo} = obj?.baz || {}; +const foo = obj?.bar +foo = obj?.bar +foo.bar = obj?.bar +bar(...obj?.foo ?? []); +var bar = {...foo?.bar}; +foo?.bar in {}; +foo?.bar < foo?.baz; +foo?.bar <= foo?.baz; +foo?.bar > foo?.baz; +foo?.bar >= foo?.baz; +[foo = obj?.bar] = []; +[foo.bar = obj?.bar] = []; +({foo = obj?.bar} = obj); +({foo: obj.bar = obj?.baz} = obj); +(foo?.bar, bar)(); +(foo?.bar ? baz : qux)(); + +async function func() { + await obj?.foo(); + await obj?.foo?.(); + (await obj?.foo)?.(); + (await obj?.foo)?.bar; + await bar?.baz; + await (foo ?? obj?.foo.baz); + (await bar?.baz ?? bar).baz; + (await bar?.baz ?? await bar).baz; + await (foo?.bar ? baz : qux); +} + +// logical operations +(obj?.foo ?? bar?.baz ?? qux)(); +((obj?.foo ?? bar?.baz) || qux)(); +((obj?.foo || bar?.baz) || qux)(); +((obj?.foo && bar?.baz) || qux)(); + +// The default value option disallowArithmeticOperators is false +obj?.foo - bar; +obj?.foo + bar; +obj?.foo * bar; +obj?.foo / bar; +obj?.foo % bar; +obj?.foo ** bar; ++obj?.foo; +-obj?.foo; +bar += obj?.foo; +bar -= obj?.foo; +bar %= obj?.foo; +bar **= obj?.foo; +bar *= obj?.boo +bar /= obj?.boo +async function func() { + await obj?.foo + await obj?.bar; + await obj?.foo - await obj?.bar; + await obj?.foo * await obj?.bar; + +await obj?.foo; + -await obj?.foo; + bar += await obj?.foo; + bar -= await obj?.foo; + bar %= await obj?.foo; + bar **= await obj?.foo; + bar *= await obj?.boo; + bar /= await obj?.boo; +} + +obj?.foo - bar; + +for (a in b?.c) {} + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js new file mode 100644 index 00000000000..292491a4927 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js @@ -0,0 +1,92 @@ +obj?.foo | bar +obj?.foo & bar +obj?.foo >> obj?.bar; +obj?.foo << obj?.bar; +obj?.foo >>> obj?.bar; +(obj?.foo || baz) + bar; +(obj?.foo ?? baz) + bar; +(obj?.foo ?? baz) - bar; +(obj?.foo ?? baz) * bar; +(obj?.foo ?? baz) / bar; +(obj?.foo ?? baz) % bar; +(obj?.foo ?? baz) ** bar; +void obj?.foo; +typeof obj?.foo; +!obj?.foo +~obj?.foo ++(obj?.foo ?? bar) +-(obj?.foo ?? bar) +bar |= obj?.foo; +bar &= obj?.foo; +bar ^= obj?.foo; +bar <<= obj?.foo; +bar >>= obj?.foo; +bar >>>= obj?.foo; +bar ||= obj?.foo +bar &&= obj?.foo +bar += (obj?.foo ?? baz); +bar -= (obj?.foo ?? baz) +bar *= (obj?.foo ?? baz) +bar /= (obj?.foo ?? baz) +bar %= (obj?.foo ?? baz); +bar **= (obj?.foo ?? baz) + +async function foo() { + (await obj?.foo || baz) + bar; + (await obj?.foo ?? baz) + bar; + (await obj?.foo ?? baz) - bar; + (await obj?.foo ?? baz) * bar; + (await obj?.foo ?? baz) / bar; + (await obj?.foo ?? baz) % bar; + (await obj?.foo ?? baz) ** bar; + void await obj?.foo; + typeof await obj?.foo; + !await obj?.foo + ~await obj?.foo + +(await obj?.foo ?? bar) + -(await obj?.foo ?? bar) + bar |= await obj?.foo; + bar &= await obj?.foo; + bar ^= await obj?.foo; + bar <<= await obj?.foo; + bar >>= await obj?.foo; + bar >>>= await obj?.foo + bar += ((await obj?.foo) ?? baz); + bar -= ((await obj?.foo) ?? baz); + bar /= ((await obj?.foo) ?? baz); + bar %= ((await obj?.foo) ?? baz); + bar **= ((await obj?.foo) ?? baz); +} + +obj?.foo + bar; +(foo || obj?.foo) + bar; +bar + (foo || obj?.foo); +(a ? obj?.foo : b) + bar +(a ? b : obj?.foo) + bar +(foo, bar, baz?.qux) + bar +obj?.foo - bar; +obj?.foo * bar; +obj?.foo / bar; +obj?.foo % bar; +obj?.foo ** bar; ++obj?.foo; +-obj?.foo; ++(foo ?? obj?.foo); ++(foo || obj?.bar); ++(obj?.bar && foo); ++(foo ? obj?.foo : bar); ++(foo ? bar : obj?.foo); +bar += obj?.foo; +bar -= obj?.foo; +bar %= obj?.foo; +bar **= obj?.foo; +bar *= obj?.boo +bar /= obj?.boo +bar += (foo ?? obj?.foo); +bar += (foo || obj?.foo); +bar += (foo && obj?.foo); +bar += (foo ? obj?.foo : bar); +bar += (foo ? bar : obj?.foo); +async function foo() { await obj?.foo + bar; } +async function foo() { (foo || await obj?.foo) + bar;} +async function foo() { bar + (foo || await obj?.foo); } diff --git a/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js.snap new file mode 100644 index 00000000000..7d60e73e9ea --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noUnsafeOptionalChaining/validArithmetic.js.snap @@ -0,0 +1,102 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: validArithmetic.js +--- +# Input +```js +obj?.foo | bar +obj?.foo & bar +obj?.foo >> obj?.bar; +obj?.foo << obj?.bar; +obj?.foo >>> obj?.bar; +(obj?.foo || baz) + bar; +(obj?.foo ?? baz) + bar; +(obj?.foo ?? baz) - bar; +(obj?.foo ?? baz) * bar; +(obj?.foo ?? baz) / bar; +(obj?.foo ?? baz) % bar; +(obj?.foo ?? baz) ** bar; +void obj?.foo; +typeof obj?.foo; +!obj?.foo +~obj?.foo ++(obj?.foo ?? bar) +-(obj?.foo ?? bar) +bar |= obj?.foo; +bar &= obj?.foo; +bar ^= obj?.foo; +bar <<= obj?.foo; +bar >>= obj?.foo; +bar >>>= obj?.foo; +bar ||= obj?.foo +bar &&= obj?.foo +bar += (obj?.foo ?? baz); +bar -= (obj?.foo ?? baz) +bar *= (obj?.foo ?? baz) +bar /= (obj?.foo ?? baz) +bar %= (obj?.foo ?? baz); +bar **= (obj?.foo ?? baz) + +async function foo() { + (await obj?.foo || baz) + bar; + (await obj?.foo ?? baz) + bar; + (await obj?.foo ?? baz) - bar; + (await obj?.foo ?? baz) * bar; + (await obj?.foo ?? baz) / bar; + (await obj?.foo ?? baz) % bar; + (await obj?.foo ?? baz) ** bar; + void await obj?.foo; + typeof await obj?.foo; + !await obj?.foo + ~await obj?.foo + +(await obj?.foo ?? bar) + -(await obj?.foo ?? bar) + bar |= await obj?.foo; + bar &= await obj?.foo; + bar ^= await obj?.foo; + bar <<= await obj?.foo; + bar >>= await obj?.foo; + bar >>>= await obj?.foo + bar += ((await obj?.foo) ?? baz); + bar -= ((await obj?.foo) ?? baz); + bar /= ((await obj?.foo) ?? baz); + bar %= ((await obj?.foo) ?? baz); + bar **= ((await obj?.foo) ?? baz); +} + +obj?.foo + bar; +(foo || obj?.foo) + bar; +bar + (foo || obj?.foo); +(a ? obj?.foo : b) + bar +(a ? b : obj?.foo) + bar +(foo, bar, baz?.qux) + bar +obj?.foo - bar; +obj?.foo * bar; +obj?.foo / bar; +obj?.foo % bar; +obj?.foo ** bar; ++obj?.foo; +-obj?.foo; ++(foo ?? obj?.foo); ++(foo || obj?.bar); ++(obj?.bar && foo); ++(foo ? obj?.foo : bar); ++(foo ? bar : obj?.foo); +bar += obj?.foo; +bar -= obj?.foo; +bar %= obj?.foo; +bar **= obj?.foo; +bar *= obj?.boo +bar /= obj?.boo +bar += (foo ?? obj?.foo); +bar += (foo || obj?.foo); +bar += (foo && obj?.foo); +bar += (foo ? obj?.foo : bar); +bar += (foo ? bar : obj?.foo); +async function foo() { await obj?.foo + bar; } +async function foo() { (foo || await obj?.foo) + bar;} +async function foo() { bar + (foo || await obj?.foo); } + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 1d3185f9ca4..c4a3f79753d 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -1044,6 +1044,9 @@ pub struct Nursery { #[doc = "Disallow control flow statements in finally blocks."] #[serde(skip_serializing_if = "Option::is_none")] pub no_unsafe_finally: Option, + #[doc = "Disallow the use of optional chaining in contexts where the undefined value is not allowed."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_unsafe_optional_chaining: Option, #[doc = "Disallow unused labels."] #[serde(skip_serializing_if = "Option::is_none")] pub no_unused_labels: Option, @@ -1116,7 +1119,7 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 60] = [ + pub(crate) const GROUP_RULES: [&'static str; 61] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -1154,6 +1157,7 @@ impl Nursery { "noSwitchDeclarations", "noUnreachableSuper", "noUnsafeFinally", + "noUnsafeOptionalChaining", "noUnusedLabels", "noUselessRename", "noUselessSwitchCase", @@ -1178,7 +1182,7 @@ impl Nursery { "useValidLang", "useYield", ]; - const RECOMMENDED_RULES: [&'static str; 49] = [ + const RECOMMENDED_RULES: [&'static str; 50] = [ "noAssignInExpressions", "noBannedTypes", "noClassAssign", @@ -1209,6 +1213,7 @@ impl Nursery { "noSwitchDeclarations", "noUnreachableSuper", "noUnsafeFinally", + "noUnsafeOptionalChaining", "noUnusedLabels", "noUselessRename", "noUselessSwitchCase", @@ -1229,7 +1234,7 @@ impl Nursery { "useValidLang", "useYield", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 49] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 50] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), @@ -1266,19 +1271,20 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -1468,121 +1474,126 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_var.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.no_void_type_return.as_ref() { + if let Some(rule) = self.no_var.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_void_type_return.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_const.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_default_parameter_last.as_ref() { + if let Some(rule) = self.use_const.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_default_switch_clause_last.as_ref() { + if let Some(rule) = self.use_default_parameter_last.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_enum_initializers.as_ref() { + if let Some(rule) = self.use_default_switch_clause_last.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_enum_initializers.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_exponentiation_operator.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exponentiation_operator.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_numeric_literals.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_numeric_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -1772,121 +1783,126 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_var.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.no_void_type_return.as_ref() { + if let Some(rule) = self.no_var.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_void_type_return.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_const.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_default_parameter_last.as_ref() { + if let Some(rule) = self.use_const.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_default_switch_clause_last.as_ref() { + if let Some(rule) = self.use_default_parameter_last.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_enum_initializers.as_ref() { + if let Some(rule) = self.use_default_switch_clause_last.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_enum_initializers.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_exponentiation_operator.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exponentiation_operator.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_numeric_literals.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_numeric_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -1895,7 +1911,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>; 49] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 50] { Self::RECOMMENDED_RULES_AS_FILTERS } pub(crate) fn get_rule_configuration(&self, rule_name: &str) -> Option<&RuleConfiguration> { @@ -1939,6 +1955,7 @@ impl Nursery { "noSwitchDeclarations" => self.no_switch_declarations.as_ref(), "noUnreachableSuper" => self.no_unreachable_super.as_ref(), "noUnsafeFinally" => self.no_unsafe_finally.as_ref(), + "noUnsafeOptionalChaining" => self.no_unsafe_optional_chaining.as_ref(), "noUnusedLabels" => self.no_unused_labels.as_ref(), "noUselessRename" => self.no_useless_rename.as_ref(), "noUselessSwitchCase" => self.no_useless_switch_case.as_ref(), diff --git a/crates/rome_service/src/configuration/parse/json/rules.rs b/crates/rome_service/src/configuration/parse/json/rules.rs index 156be8ceb5d..76a52bf80bd 100644 --- a/crates/rome_service/src/configuration/parse/json/rules.rs +++ b/crates/rome_service/src/configuration/parse/json/rules.rs @@ -740,6 +740,7 @@ impl VisitNode for Nursery { "noSwitchDeclarations", "noUnreachableSuper", "noUnsafeFinally", + "noUnsafeOptionalChaining", "noUnusedLabels", "noUselessRename", "noUselessSwitchCase", @@ -1445,6 +1446,24 @@ impl VisitNode for Nursery { )); } }, + "noUnsafeOptionalChaining" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_unsafe_optional_chaining = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_object(&value, name_text, &mut configuration, diagnostics)?; + self.no_unsafe_optional_chaining = Some(configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noUnusedLabels" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 17dbdede68a..4485c53933d 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -648,6 +648,13 @@ { "type": "null" } ] }, + "noUnsafeOptionalChaining": { + "description": "Disallow the use of optional chaining in contexts where the undefined value is not allowed.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noUnusedLabels": { "description": "Disallow unused labels.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index e12527323fe..7dcebf231bf 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -432,6 +432,10 @@ export interface Nursery { * Disallow control flow statements in finally blocks. */ noUnsafeFinally?: RuleConfiguration; + /** + * Disallow the use of optional chaining in contexts where the undefined value is not allowed. + */ + noUnsafeOptionalChaining?: RuleConfiguration; /** * Disallow unused labels. */ @@ -853,6 +857,7 @@ export type Category = | "lint/nursery/noUselessRename" | "lint/nursery/useValidForDirection" | "lint/nursery/useHookAtTopLevel" + | "lint/nursery/noUnsafeOptionalChaining" | "lint/nursery/noDuplicateJsxProps" | "lint/nursery/noDuplicateClassMembers" | "lint/nursery/useYield" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 17dbdede68a..4485c53933d 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -648,6 +648,13 @@ { "type": "null" } ] }, + "noUnsafeOptionalChaining": { + "description": "Disallow the use of optional chaining in contexts where the undefined value is not allowed.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noUnusedLabels": { "description": "Disallow unused labels.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 7951a84a72d..2b1d82b75fc 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -710,6 +710,12 @@ a superclass Disallow control flow statements in finally blocks.
+

+ noUnsafeOptionalChaining +

+Disallow the use of optional chaining in contexts where the undefined value is not allowed. +
+

noUnusedLabels

diff --git a/website/src/pages/lint/rules/noUnsafeOptionalChaining.md b/website/src/pages/lint/rules/noUnsafeOptionalChaining.md new file mode 100644 index 00000000000..7b22e105cbd --- /dev/null +++ b/website/src/pages/lint/rules/noUnsafeOptionalChaining.md @@ -0,0 +1,172 @@ +--- +title: Lint Rule noUnsafeOptionalChaining +parent: lint/rules/index +--- + +# noUnsafeOptionalChaining (since v12.0.0) + +Disallow the use of optional chaining in contexts where the undefined value is not allowed. + +The optional chaining (?.) expression can short-circuit with a return value of undefined. +Therefore, treating an evaluated optional chaining expression as a function, object, number, etc., can cause TypeError or unexpected results. +Also, parentheses limit the scope of short-circuiting in chains. + +## Examples + +### Invalid + +```jsx +1 in obj?.foo; +``` + +
nursery/noUnsafeOptionalChaining.js:1:9 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ 1 in obj?.foo;
+           ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ 1 in obj?.foo;
+   ^^^^^^^^^^^^^
+    2 │ 
+  
+
+ +```js +with (obj?.foo); +``` + +
nursery/noUnsafeOptionalChaining.js:1:10 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ with (obj?.foo);
+            ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ with (obj?.foo);
+   ^^^^^^^^^^^^^^^^
+    2 │ 
+  
+
+ +```jsx +for (bar of obj?.foo); +``` + +
nursery/noUnsafeOptionalChaining.js:1:16 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ for (bar of obj?.foo);
+                  ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ for (bar of obj?.foo);
+   ^^^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+
+ +```jsx +bar instanceof obj?.foo; +``` + +
nursery/noUnsafeOptionalChaining.js:1:19 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ bar instanceof obj?.foo;
+                     ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ bar instanceof obj?.foo;
+   ^^^^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+
+ +```jsx +const { bar } = obj?.foo; +``` + +
nursery/noUnsafeOptionalChaining.js:1:20 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ const { bar } = obj?.foo;
+                      ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ const { bar } = obj?.foo;
+         ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+
+ +```jsx +(obj?.foo)(); +``` + +
nursery/noUnsafeOptionalChaining.js:1:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ (obj?.foo)();
+       ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ (obj?.foo)();
+             ^^
+    2 │ 
+  
+
+ +```jsx +(baz?.bar).foo; +``` + +
nursery/noUnsafeOptionalChaining.js:1:5 lint/nursery/noUnsafeOptionalChaining ━━━━━━━━━━━━━━━━━━━━━━
+
+   Unsafe usage of optional chaining.
+  
+  > 1 │ (baz?.bar).foo;
+       ^^
+    2 │ 
+  
+   If it short-circuits with 'undefined' the evaluation will throw TypeError here:
+  
+  > 1 │ (baz?.bar).foo;
+              ^^^
+    2 │ 
+  
+
+ +## Valid + +```jsx +(obj?.foo)?.(); +obj?.foo(); +(obj?.foo ?? bar)(); +obj?.foo.bar; +obj.foo?.bar; +foo?.()?.bar; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)