-
-
Notifications
You must be signed in to change notification settings - Fork 477
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): implement @typescript-eslint/no-non-null-asserted-nulli… (
#3850) …sh-coalescing Related issue: #2180 original implementation - code: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts - test: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/tests/rules/no-non-null-asserted-nullish-coalescing.test.ts - doc: https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing/
- Loading branch information
Showing
3 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
crates/oxc_linter/src/rules/typescript/no_non_null_asserted_nullish_coalescing.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
121 changes: 121 additions & 0 deletions
121
crates/oxc_linter/src/snapshots/no_non_null_asserted_nullish_coalescing.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |