Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ mod typescript {
pub mod no_import_type_side_effects;
pub mod no_misused_new;
pub mod no_namespace;
pub mod no_non_null_asserted_nullish_coalescing;
pub mod no_non_null_asserted_optional_chain;
pub mod no_non_null_assertion;
pub mod no_this_alias;
Expand Down Expand Up @@ -542,6 +543,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::prefer_literal_enum_member,
typescript::explicit_function_return_type,
typescript::no_non_null_assertion,
typescript::no_non_null_asserted_nullish_coalescing,
jest::expect_expect,
jest::max_expects,
jest::max_nested_describe,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::SymbolId;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};

#[derive(Debug, Default, Clone)]
pub struct NoNonNullAssertedNullishCoalescing;

declare_oxc_lint!(
/// ### What it does
/// Disallow non-null assertions in the left operand of a nullish coalescing operator.
///
/// ### Why is this bad?
/// The ?? nullish coalescing runtime operator allows providing a default value when dealing with null or undefined. Using a ! non-null assertion type operator in the left operand of a nullish coalescing operator is redundant, and likely a sign of programmer error or confusion over the two operators.
///
/// ### Example
/// ```javascript
/// foo! ?? bar;
///
/// let x: string;
/// x! ?? '';
/// ```
NoNonNullAssertedNullishCoalescing,
restriction,
);

fn no_non_null_asserted_nullish_coalescing_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator")
.with_help("The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.")
.with_labels([span0.into()])
}

impl Rule for NoNonNullAssertedNullishCoalescing {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::LogicalExpression(expr) = node.kind() else { return };
let Expression::TSNonNullExpression(ts_non_null_expr) = &expr.left else { return };
if let Expression::Identifier(ident) = &ts_non_null_expr.expression {
if let Some(symbol_id) = ctx.scopes().get_binding(node.scope_id(), &ident.name) {
if !has_assignment_before_node(symbol_id, ctx, expr.span.end) {
return;
}
}
}

ctx.diagnostic(no_non_null_asserted_nullish_coalescing_diagnostic(ts_non_null_expr.span));
}
}
fn has_assignment_before_node(
symbol_id: SymbolId,
ctx: &LintContext,
parent_span_end: u32,
) -> bool {
let symbol_table = ctx.semantic().symbols();

for reference in symbol_table.get_resolved_references(symbol_id) {
if reference.is_write() && reference.span().end < parent_span_end {
return true;
}
}

let declaration_id = symbol_table.get_declaration(symbol_id);
let AstKind::VariableDeclarator(decl) = ctx.nodes().kind(declaration_id) else {
return false;
};
decl.definite || decl.init.is_some()
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
"foo ?? bar;",
"foo ?? bar!;",
"foo.bazz ?? bar;",
"foo.bazz ?? bar!;",
"foo!.bazz ?? bar;",
"foo!.bazz ?? bar!;",
"foo() ?? bar;",
"foo() ?? bar!;",
"(foo ?? bar)!;",
"
let x: string;
x! ?? '';
",
"
let x: string;
x ?? '';
",
"
let x!: string;
x ?? '';
",
"
let x: string;
foo(x);
x! ?? '';
",
"
let x: string;
x! ?? '';
x = foo();
",
"
let x: string;
foo(x);
x! ?? '';
x = foo();
",
"
let x = foo();
x ?? '';
",
"
function foo() {
let x: string;
return x ?? '';
}
",
"
let x: string;
function foo() {
return x ?? '';
}
",
];

let fail = vec![
"foo! ?? bar;",
"foo! ?? bar!;",
"foo.bazz! ?? bar;",
"foo.bazz! ?? bar!;",
"foo!.bazz! ?? bar;",
"foo!.bazz! ?? bar!;",
"foo()! ?? bar;",
"foo()! ?? bar!;",
"
let x!: string;
x! ?? '';
",
"
let x: string;
x = foo();
x! ?? '';
",
"
let x: string;
x = foo();
x! ?? '';
x = foo();
",
"
let x = foo();
x! ?? '';
",
"
function foo() {
let x!: string;
return x! ?? '';
}
",
"
let x!: string;
function foo() {
return x! ?? '';
}
",
"
let x = foo();
x ! ?? '';
",
];

Tester::new(NoNonNullAssertedNullishCoalescing::NAME, pass, fail).test_and_snapshot();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
source: crates/oxc_linter/src/tester.rs
---
⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo! ?? bar;
· ────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo! ?? bar!;
· ────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo.bazz! ?? bar;
· ─────────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo.bazz! ?? bar!;
· ─────────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo!.bazz! ?? bar;
· ──────────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo!.bazz! ?? bar!;
· ──────────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo()! ?? bar;
· ──────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:1:1]
1 │ foo()! ?? bar!;
· ──────
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:3:10]
2 │ let x!: string;
3 │ x! ?? '';
· ──
4 │
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:4:10]
3 │ x = foo();
4 │ x! ?? '';
· ──
5 │
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:4:10]
3 │ x = foo();
4 │ x! ?? '';
· ──
5 │ x = foo();
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:3:10]
2 │ let x = foo();
3 │ x! ?? '';
· ──
4 │
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:4:19]
3 │ let x!: string;
4 │ return x! ?? '';
· ──
5 │ }
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:4:19]
3 │ function foo() {
4 │ return x! ?? '';
· ──
5 │ }
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.

⚠ typescript-eslint(no-non-null-asserted-nullish-coalescing): 'Disallow non-null assertions in the left operand of a nullish coalescing operator
╭─[no_non_null_asserted_nullish_coalescing.tsx:3:10]
2 │ let x = foo();
3 │ x ! ?? '';
· ────
4 │
╰────
help: The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.