diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 389c867530e..583333fabe6 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -74,23 +74,23 @@ define_dategories! { "lint/security/noDangerouslySetInnerHtml": "https://docs.rome.tools/lint/rules/noDangerouslySetInnerHtml", "lint/security/noDangerouslySetInnerHtmlWithChildren": "https://docs.rome.tools/lint/rules/noDangerouslySetInnerHtmlWithChildren", - // nursery "lint/nursery/noAccessKey": "https://docs.rome.tools/lint/rules/noAccessKey", "lint/nursery/noBannedTypes":"https://docs.rome.tools/lint/rules/noBannedTypes", "lint/nursery/noConditionalAssignment": "https://docs.rome.tools/lint/rules/noConditionalAssignment", "lint/nursery/noConstAssign": "https://docs.rome.tools/lint/rules/noConstAssign", "lint/nursery/noConstEnum": "https://docs.rome.tools/lint/rules/noConstEnum", - "lint/nursery/noDistractingElements": "https://docs.rome.tools/lint/rules/noDistractingElements", "lint/nursery/noConstructorReturn": "https://docs.rome.tools/lint/rules/noConstructorReturn", - "lint/nursery/noSetterReturn": "https://docs.rome.tools/lint/rules/noSetterReturn", + "lint/nursery/noDistractingElements": "https://docs.rome.tools/lint/rules/noDistractingElements", "lint/nursery/noDupeKeys":"https://docs.rome.tools/lint/rules/noDupeKeys", "lint/nursery/noEmptyInterface": "https://docs.rome.tools/lint/rules/noEmptyInterface", "lint/nursery/noExplicitAny": "https://docs.rome.tools/lint/rules/noExplicitAny", "lint/nursery/noExtraNonNullAssertion":"https://docs.rome.tools/lint/rules/noExtraNonNullAssertion", "lint/nursery/noHeaderScope": "https://docs.rome.tools/lint/rules/noHeaderScope", "lint/nursery/noInvalidConstructorSuper": "https://docs.rome.tools/lint/rules/noInvalidConstructorSuper", + "lint/nursery/noNonNullAssertion": "https://docs.rome.tools/lint/rules/noNonNullAssertion", "lint/nursery/noPrecisionLoss": "https://docs.rome.tools/lint/rules/noPrecisionLoss", + "lint/nursery/noSetterReturn": "https://docs.rome.tools/lint/rules/noSetterReturn", "lint/nursery/noStringCaseMismatch": "https://docs.rome.tools/lint/rules/noStringCaseMismatch", "lint/nursery/noUnsafeFinally": "https://docs.rome.tools/lint/rules/noUnsafeFinally", "lint/nursery/noVar": "https://docs.rome.tools/lint/rules/noVar", diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index d4ef850f6a3..1ca4ee58940 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -13,6 +13,7 @@ mod no_explicit_any; mod no_extra_non_null_assertion; mod no_header_scope; mod no_invalid_constructor_super; +mod no_non_null_assertion; mod no_precision_loss; mod no_setter_return; mod no_string_case_mismatch; @@ -22,4 +23,4 @@ mod use_default_switch_clause_last; mod use_flat_map; mod use_numeric_literals; mod use_valid_for_direction; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_banned_types :: NoBannedTypes , self :: no_conditional_assignment :: NoConditionalAssignment , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_dupe_keys :: NoDupeKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_explicit_any :: NoExplicitAny , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_precision_loss :: NoPrecisionLoss , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_void_type_return :: NoVoidTypeReturn , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } } +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_banned_types :: NoBannedTypes , self :: no_conditional_assignment :: NoConditionalAssignment , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_dupe_keys :: NoDupeKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_explicit_any :: NoExplicitAny , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_void_type_return :: NoVoidTypeReturn , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_non_null_assertion.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_non_null_assertion.rs new file mode 100644 index 00000000000..ec3200b88de --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_non_null_assertion.rs @@ -0,0 +1,160 @@ +use crate::JsRuleAction; +use rome_analyze::{context::RuleContext, declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_diagnostics::Applicability; +use rome_js_factory::make; +use rome_js_syntax::{ + AnyJsExpression, JsSyntaxKind, TsNonNullAssertionAssignment, TsNonNullAssertionExpression, +}; +use rome_rowan::{declare_node_union, AstNode, BatchMutationExt}; + +declare_rule! { + /// Disallow non-null assertions using the `!` postfix operator. + /// + /// TypeScript's `!` non-null assertion operator asserts to the type system that an expression is non-nullable, as + /// in not `null` or `undefined`. Using assertions to tell the type system new information is often a sign that + /// code is not fully type-safe. It's generally better to structure program logic so that TypeScript understands + /// when values may be nullable. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```ts,expect_diagnostic + /// interface Example { + /// property?: string; + /// } + /// declare const example: Example; + /// const includesBaz = foo.property!.includes('baz'); + /// ``` + /// ```ts,expect_diagnostic + /// (b!! as number) = "test"; + /// ``` + /// + /// ### Valid + /// + /// ```ts + /// interface Example { + /// property?: string; + /// } + /// + /// declare const example: Example; + /// const includesBaz = foo.property?.includes('baz') ?? false; + /// ``` + /// + pub(crate) NoNonNullAssertion { + version: "11.0.0", + name: "noNonNullAssertion", + recommended: false, + } +} + +declare_node_union! { + pub(crate) AnyTsNonNullAssertion = TsNonNullAssertionExpression | TsNonNullAssertionAssignment +} + +impl Rule for NoNonNullAssertion { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + match ctx.query() { + AnyTsNonNullAssertion::TsNonNullAssertionExpression(node) => node + .parent::() + .map_or(Some(()), |_| None), + AnyTsNonNullAssertion::TsNonNullAssertionAssignment(node) => node + .parent::() + .map_or(Some(()), |_| None), + } + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + markup! { + "Forbidden non-null assertion." + }, + )) + } + + fn action(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + match node { + AnyTsNonNullAssertion::TsNonNullAssertionAssignment(_) => None, + AnyTsNonNullAssertion::TsNonNullAssertionExpression(node) => { + if !can_replace_with_optional_chain(node) { + return None; + } + let mut mutation = ctx.root().begin(); + + let assertions = + std::iter::successors(node.expression().ok(), |expression| match expression { + AnyJsExpression::TsNonNullAssertionExpression(assertion) => { + assertion.expression().ok() + } + _ => None, + }); + + for assertion in assertions { + if let Some(non_null_expr) = assertion.as_ts_non_null_assertion_expression() { + mutation.remove_token(non_null_expr.excl_token().ok()?); + } + } + + match node.parent::()? { + AnyJsExpression::JsComputedMemberExpression(parent) => { + if parent.is_optional() { + mutation.remove_token(node.excl_token().ok()?); + } else { + mutation.replace_token( + node.excl_token().ok()?, + make::token(JsSyntaxKind::QUESTIONDOT), + ); + } + } + AnyJsExpression::JsCallExpression(parent) => { + if parent.is_optional() { + mutation.remove_token(node.excl_token().ok()?); + } else { + mutation.replace_token( + node.excl_token().ok()?, + make::token(JsSyntaxKind::QUESTIONDOT), + ); + } + } + AnyJsExpression::JsStaticMemberExpression(parent) => { + if parent.is_optional() { + mutation.remove_token(node.excl_token().ok()?); + } else { + mutation.replace_token( + node.excl_token().ok()?, + make::token(JsSyntaxKind::QUESTION), + ); + } + } + _ => {} + }; + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { "Replace with optional chain operator ""?."" This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator" } + .to_owned(), + mutation, + }) + } + } + } +} + +fn can_replace_with_optional_chain(node: &TsNonNullAssertionExpression) -> bool { + use AnyJsExpression::*; + + matches!( + node.parent::(), + Some(JsStaticMemberExpression(_) | JsComputedMemberExpression(_) | JsCallExpression(_)) + ) +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts new file mode 100644 index 00000000000..15c75315302 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts @@ -0,0 +1,20 @@ +x!; +x!.y; +x.y!; +!x!.y; +x!.y?.z; +x![y]; +x![y]?.z; +x.y.z!(); +x.y?.z!(); +x!!!; +x!!.y; +x.y!!; +x.y.z!!(); +x!?.[y].z; +x!?.y.z; +x!!!?.y.z; +x.y.z!?.(); +x.y.z!!!?.(); +(b! as number) = "test"; +(b!! as number) = "test"; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts.snap b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts.snap new file mode 100644 index 00000000000..8271b09f6e3 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/invalid.ts.snap @@ -0,0 +1,427 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```js +x!; +x!.y; +x.y!; +!x!.y; +x!.y?.z; +x![y]; +x![y]?.z; +x.y.z!(); +x.y?.z!(); +x!!!; +x!!.y; +x.y!!; +x.y.z!!(); +x!?.[y].z; +x!?.y.z; +x!!!?.y.z; +x.y.z!?.(); +x.y.z!!!?.(); +(b! as number) = "test"; +(b!! as number) = "test"; + +``` + +# Diagnostics +``` +invalid.ts:1:1 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + > 1 │ x!; + │ ^^ + 2 │ x!.y; + 3 │ x.y!; + + +``` + +``` +invalid.ts:2:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 1 │ x!; + > 2 │ x!.y; + │ ^^ + 3 │ x.y!; + 4 │ !x!.y; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 1 1 │ x!; + 2 │ - x!.y; + 2 │ + x?.y; + 3 3 │ x.y!; + 4 4 │ !x!.y; + + +``` + +``` +invalid.ts:3:1 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 1 │ x!; + 2 │ x!.y; + > 3 │ x.y!; + │ ^^^^ + 4 │ !x!.y; + 5 │ x!.y?.z; + + +``` + +``` +invalid.ts:4:2 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 2 │ x!.y; + 3 │ x.y!; + > 4 │ !x!.y; + │ ^^ + 5 │ x!.y?.z; + 6 │ x![y]; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 2 2 │ x!.y; + 3 3 │ x.y!; + 4 │ - !x!.y; + 4 │ + !x?.y; + 5 5 │ x!.y?.z; + 6 6 │ x![y]; + + +``` + +``` +invalid.ts:5:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 3 │ x.y!; + 4 │ !x!.y; + > 5 │ x!.y?.z; + │ ^^ + 6 │ x![y]; + 7 │ x![y]?.z; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 3 3 │ x.y!; + 4 4 │ !x!.y; + 5 │ - x!.y?.z; + 5 │ + x?.y?.z; + 6 6 │ x![y]; + 7 7 │ x![y]?.z; + + +``` + +``` +invalid.ts:6:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 4 │ !x!.y; + 5 │ x!.y?.z; + > 6 │ x![y]; + │ ^^ + 7 │ x![y]?.z; + 8 │ x.y.z!(); + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 4 4 │ !x!.y; + 5 5 │ x!.y?.z; + 6 │ - x![y]; + 6 │ + x?.[y]; + 7 7 │ x![y]?.z; + 8 8 │ x.y.z!(); + + +``` + +``` +invalid.ts:7:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 5 │ x!.y?.z; + 6 │ x![y]; + > 7 │ x![y]?.z; + │ ^^ + 8 │ x.y.z!(); + 9 │ x.y?.z!(); + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 5 5 │ x!.y?.z; + 6 6 │ x![y]; + 7 │ - x![y]?.z; + 7 │ + x?.[y]?.z; + 8 8 │ x.y.z!(); + 9 9 │ x.y?.z!(); + + +``` + +``` +invalid.ts:8:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 6 │ x![y]; + 7 │ x![y]?.z; + > 8 │ x.y.z!(); + │ ^^^^^^ + 9 │ x.y?.z!(); + 10 │ x!!!; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 6 6 │ x![y]; + 7 7 │ x![y]?.z; + 8 │ - x.y.z!(); + 8 │ + x.y.z?.(); + 9 9 │ x.y?.z!(); + 10 10 │ x!!!; + + +``` + +``` +invalid.ts:9:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 7 │ x![y]?.z; + 8 │ x.y.z!(); + > 9 │ x.y?.z!(); + │ ^^^^^^^ + 10 │ x!!!; + 11 │ x!!.y; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 7 7 │ x![y]?.z; + 8 8 │ x.y.z!(); + 9 │ - x.y?.z!(); + 9 │ + x.y?.z?.(); + 10 10 │ x!!!; + 11 11 │ x!!.y; + + +``` + +``` +invalid.ts:10:1 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 8 │ x.y.z!(); + 9 │ x.y?.z!(); + > 10 │ x!!!; + │ ^^^^ + 11 │ x!!.y; + 12 │ x.y!!; + + +``` + +``` +invalid.ts:11:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 9 │ x.y?.z!(); + 10 │ x!!!; + > 11 │ x!!.y; + │ ^^^ + 12 │ x.y!!; + 13 │ x.y.z!!(); + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 9 9 │ x.y?.z!(); + 10 10 │ x!!!; + 11 │ - x!!.y; + 11 │ + x?.y; + 12 12 │ x.y!!; + 13 13 │ x.y.z!!(); + + +``` + +``` +invalid.ts:12:1 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 10 │ x!!!; + 11 │ x!!.y; + > 12 │ x.y!!; + │ ^^^^^ + 13 │ x.y.z!!(); + 14 │ x!?.[y].z; + + +``` + +``` +invalid.ts:13:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 11 │ x!!.y; + 12 │ x.y!!; + > 13 │ x.y.z!!(); + │ ^^^^^^^ + 14 │ x!?.[y].z; + 15 │ x!?.y.z; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 11 11 │ x!!.y; + 12 12 │ x.y!!; + 13 │ - x.y.z!!(); + 13 │ + x.y.z?.(); + 14 14 │ x!?.[y].z; + 15 15 │ x!?.y.z; + + +``` + +``` +invalid.ts:14:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 12 │ x.y!!; + 13 │ x.y.z!!(); + > 14 │ x!?.[y].z; + │ ^^ + 15 │ x!?.y.z; + 16 │ x!!!?.y.z; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 14 │ x!?.[y].z; + │ - + +``` + +``` +invalid.ts:15:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 13 │ x.y.z!!(); + 14 │ x!?.[y].z; + > 15 │ x!?.y.z; + │ ^^ + 16 │ x!!!?.y.z; + 17 │ x.y.z!?.(); + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 15 │ x!?.y.z; + │ - + +``` + +``` +invalid.ts:16:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 14 │ x!?.[y].z; + 15 │ x!?.y.z; + > 16 │ x!!!?.y.z; + │ ^^^^ + 17 │ x.y.z!?.(); + 18 │ x.y.z!!!?.(); + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 16 │ x!!!?.y.z; + │ --- + +``` + +``` +invalid.ts:17:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 15 │ x!?.y.z; + 16 │ x!!!?.y.z; + > 17 │ x.y.z!?.(); + │ ^^^^^^ + 18 │ x.y.z!!!?.(); + 19 │ (b! as number) = "test"; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 17 │ x.y.z!?.(); + │ - + +``` + +``` +invalid.ts:18:1 lint/nursery/noNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 16 │ x!!!?.y.z; + 17 │ x.y.z!?.(); + > 18 │ x.y.z!!!?.(); + │ ^^^^^^^^ + 19 │ (b! as number) = "test"; + 20 │ (b!! as number) = "test"; + + i Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator + + 18 │ x.y.z!!!?.(); + │ --- + +``` + +``` +invalid.ts:19:2 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 17 │ x.y.z!?.(); + 18 │ x.y.z!!!?.(); + > 19 │ (b! as number) = "test"; + │ ^^ + 20 │ (b!! as number) = "test"; + 21 │ + + +``` + +``` +invalid.ts:20:2 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Forbidden non-null assertion. + + 18 │ x.y.z!!!?.(); + 19 │ (b! as number) = "test"; + > 20 │ (b!! as number) = "test"; + │ ^^^ + 21 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts new file mode 100644 index 00000000000..83d1de9445e --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts @@ -0,0 +1,6 @@ +x; +x.y; +x.y.z; +x?.y.z; +x?.y?.z; +!x; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts.snap b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts.snap new file mode 100644 index 00000000000..18797edb6ec --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noNonNullAssertion/valid.ts.snap @@ -0,0 +1,16 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```js +x; +x.y; +x.y.z; +x?.y.z; +x?.y?.z; +!x; + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 0cdde4fa2da..faed9a24bb1 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -755,6 +755,8 @@ struct NurserySchema { no_header_scope: Option, #[doc = "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors."] no_invalid_constructor_super: Option, + #[doc = "Disallow non-null assertions using the ! postfix operator."] + no_non_null_assertion: Option, #[doc = "Disallow literal numbers that lose precision"] no_precision_loss: Option, #[doc = "Disallow returning a value from a setter"] @@ -788,7 +790,7 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 28] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 29] = [ "noAccessKey", "noBannedTypes", "noConditionalAssignment", @@ -802,6 +804,7 @@ impl Nursery { "noExtraNonNullAssertion", "noHeaderScope", "noInvalidConstructorSuper", + "noNonNullAssertion", "noPrecisionLoss", "noSetterReturn", "noStringCaseMismatch", diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 6b6d01ab955..00761d15c69 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -896,6 +896,17 @@ } ] }, + "noNonNullAssertion": { + "description": "Disallow non-null assertions using the ! postfix operator.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noPrecisionLoss": { "description": "Disallow literal numbers that lose precision", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 4537e4493f2..71a7755475f 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -399,6 +399,10 @@ export interface Nursery { * Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors. */ noInvalidConstructorSuper?: RuleConfiguration; + /** + * Disallow non-null assertions using the ! postfix operator. + */ + noNonNullAssertion?: RuleConfiguration; /** * Disallow literal numbers that lose precision */ @@ -672,16 +676,17 @@ export type Category = | "lint/nursery/noConditionalAssignment" | "lint/nursery/noConstAssign" | "lint/nursery/noConstEnum" - | "lint/nursery/noDistractingElements" | "lint/nursery/noConstructorReturn" - | "lint/nursery/noSetterReturn" + | "lint/nursery/noDistractingElements" | "lint/nursery/noDupeKeys" | "lint/nursery/noEmptyInterface" | "lint/nursery/noExplicitAny" | "lint/nursery/noExtraNonNullAssertion" | "lint/nursery/noHeaderScope" | "lint/nursery/noInvalidConstructorSuper" + | "lint/nursery/noNonNullAssertion" | "lint/nursery/noPrecisionLoss" + | "lint/nursery/noSetterReturn" | "lint/nursery/noStringCaseMismatch" | "lint/nursery/noUnsafeFinally" | "lint/nursery/noVar" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 6b6d01ab955..00761d15c69 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -896,6 +896,17 @@ } ] }, + "noNonNullAssertion": { + "description": "Disallow non-null assertions using the ! postfix operator.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noPrecisionLoss": { "description": "Disallow literal numbers that lose precision", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index c52e3b33c9b..3573d255d99 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -523,6 +523,12 @@ Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors.
+

+ noNonNullAssertion +

+Disallow non-null assertions using the ! postfix operator. +
+

noPrecisionLoss

diff --git a/website/src/pages/lint/rules/noNonNullAssertion.md b/website/src/pages/lint/rules/noNonNullAssertion.md new file mode 100644 index 00000000000..2b9f32ba53d --- /dev/null +++ b/website/src/pages/lint/rules/noNonNullAssertion.md @@ -0,0 +1,71 @@ +--- +title: Lint Rule noNonNullAssertion +parent: lint/rules/index +--- + +# noNonNullAssertion (since v11.0.0) + +Disallow non-null assertions using the `!` postfix operator. + +TypeScript's `!` non-null assertion operator asserts to the type system that an expression is non-nullable, as +in not `null` or `undefined`. Using assertions to tell the type system new information is often a sign that +code is not fully type-safe. It's generally better to structure program logic so that TypeScript understands +when values may be nullable. + +## Examples + +### Invalid + +```ts +interface Example { + property?: string; +} +declare const example: Example; +const includesBaz = foo.property!.includes('baz'); +``` + +
nursery/noNonNullAssertion.js:5:21 lint/nursery/noNonNullAssertion  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━
+
+   Forbidden non-null assertion.
+  
+    3 │ }
+    4 │ declare const example: Example;
+  > 5 │ const includesBaz = foo.property!.includes('baz');
+                       ^^^^^^^^^^^^^
+    6 │ 
+  
+   Suggested fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator
+  
+    3 3  }
+    4 4  declare const example: Example;
+    5  - const·includesBaz·=·foo.property!.includes('baz');
+      5+ const·includesBaz·=·foo.property?.includes('baz');
+    6 6  
+  
+
+ +```ts +(b!! as number) = "test"; +``` + +
nursery/noNonNullAssertion.js:1:2 lint/nursery/noNonNullAssertion ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Forbidden non-null assertion.
+  
+  > 1 │ (b!! as number) = "test";
+    ^^^
+    2 │ 
+  
+
+ +### Valid + +```ts +interface Example { + property?: string; +} + +declare const example: Example; +const includesBaz = foo.property?.includes('baz') ?? false; +``` +