From 8c61f9c0b4d4083a2578d27d1988b94bd9a00644 Mon Sep 17 00:00:00 2001 From: kaykdm <34934746+kaykdm@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:09:52 +0900 Subject: [PATCH] feat(linter): implement @typescript-eslint/no-non-null-assertion (#3825) Related issue: https://github.com/oxc-project/oxc/issues/2180 original implementation - code: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/no-non-null-assertion.ts - test: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts - doc: https://typescript-eslint.io/rules/no-non-null-assertion/ typescript-eslint has a feature that is manually fixable by editor suggestions on this rule, but oxc does not have one as far as I know, so the implementation is simple compared to typescript-eslint --- crates/oxc_linter/src/rules.rs | 2 + .../rules/typescript/no_non_null_assertion.rs | 87 ++++++++ .../src/snapshots/no_non_null_assertion.snap | 185 ++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 crates/oxc_linter/src/rules/typescript/no_non_null_assertion.rs create mode 100644 crates/oxc_linter/src/snapshots/no_non_null_assertion.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 1adec6038e6e3..b89f807fb1514 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -137,6 +137,7 @@ mod typescript { pub mod no_misused_new; pub mod no_namespace; pub mod no_non_null_asserted_optional_chain; + pub mod no_non_null_assertion; pub mod no_this_alias; pub mod no_unnecessary_type_constraint; pub mod no_unsafe_declaration_merging; @@ -540,6 +541,7 @@ oxc_macros::declare_all_lint_rules! { typescript::triple_slash_reference, typescript::prefer_literal_enum_member, typescript::explicit_function_return_type, + typescript::no_non_null_assertion, jest::expect_expect, jest::max_expects, jest::max_nested_describe, diff --git a/crates/oxc_linter/src/rules/typescript/no_non_null_assertion.rs b/crates/oxc_linter/src/rules/typescript/no_non_null_assertion.rs new file mode 100644 index 0000000000000..5d86e19fb8aa1 --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_non_null_assertion.rs @@ -0,0 +1,87 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Default, Clone)] +pub struct NoNonNullAssertion; + +declare_oxc_lint!( + /// ### What it does + /// Disallow non-null assertions using the ! postfix operator. + /// + /// ### Why is this bad? + /// 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. + /// + /// ### Example + /// ```javascript + /// x!; + /// x!.y; + /// x.y!; + /// ``` + NoNonNullAssertion, + restriction, +); + +fn no_non_null_assertion_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("typescript-eslint(no-non-null-assertion): Forbidden non-null assertion.") + .with_help("Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator.") + .with_labels([span0.into()]) +} + +impl Rule for NoNonNullAssertion { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::TSNonNullExpression(expr) = node.kind() else { return }; + ctx.diagnostic(no_non_null_assertion_diagnostic(expr.span)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec!["x;", "x.y;", "x.y.z;", "x?.y.z;", "x?.y?.z;", "!x;"]; + + let fail = vec![ + "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 + ", + " + x! + // comment + .y + ", + " + x! + // comment + . /* comment */ + y + ", + " + x! + // comment + /* comment */ ['y'] + ", + ]; + + Tester::new(NoNonNullAssertion::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_non_null_assertion.snap b/crates/oxc_linter/src/snapshots/no_non_null_assertion.snap new file mode 100644 index 0000000000000..fa671827a29c3 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_non_null_assertion.snap @@ -0,0 +1,185 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!.y; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y!; + · ──── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:2] + 1 │ !x!.y; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!.y?.z; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x![y]; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x![y]?.z; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y.z!(); + · ────── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y?.z!(); + · ─────── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!!!; + · ──── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!!!; + · ─── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!!!; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!!.y; + · ─── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!!.y; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y!!; + · ───── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y!!; + · ──── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y.z!!(); + · ─────── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y.z!!(); + · ────── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!?.[y].z; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x!?.y.z; + · ── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:1:1] + 1 │ x.y.z!?.(); + · ────── + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:2:10] + 1 │ + 2 │ x! + · ── + 3 │ .y + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:2:10] + 1 │ + 2 │ x! + · ── + 3 │ // comment + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:2:10] + 1 │ + 2 │ x! + · ── + 3 │ // comment + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator. + + ⚠ typescript-eslint(no-non-null-assertion): Forbidden non-null assertion. + ╭─[no_non_null_assertion.tsx:2:10] + 1 │ + 2 │ x! + · ── + 3 │ // comment + ╰──── + help: Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator.