Skip to content

Commit ad06194

Browse files
committedMar 24, 2025·
refactor(linter): add fixer for typescript-eslint/no-non-null-asserted-optional-chain (#9993)
Adds fixes for the `typescript-eslint/no-non-null-asserted-optional-chain` rule in the linter and adds better diagnostic messages with labeled spans.
1 parent 2d7b0cf commit ad06194

File tree

3 files changed

+127
-59
lines changed

3 files changed

+127
-59
lines changed
 

Diff for: ‎crates/oxc_language_server/src/linter/snapshots/fixtures_linter_issue_9958_issue.ts.snap

+18-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,26 @@ tags: None
1515

1616
code: "typescript-eslint(no-non-null-asserted-optional-chain)"
1717
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/typescript_eslint/no-non-null-asserted-optional-chain"
18-
message: "non-null assertions after an optional chain expression\nhelp: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion."
19-
range: Range { start: Position { line: 11, character: 18 }, end: Position { line: 11, character: 18 } }
20-
related_information[0].message: ""
18+
message: "Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.\nhelp: Remove the non-null assertion."
19+
range: Range { start: Position { line: 11, character: 18 }, end: Position { line: 11, character: 19 } }
20+
related_information[0].message: "non-null assertion made after optional chain"
2121
related_information[0].location.uri: "file://<variable>/fixtures/linter/issue_9958/issue.ts"
22-
related_information[0].location.range: Range { start: Position { line: 11, character: 18 }, end: Position { line: 11, character: 18 } }
23-
related_information[1].message: ""
22+
related_information[0].location.range: Range { start: Position { line: 11, character: 21 }, end: Position { line: 11, character: 22 } }
23+
related_information[1].message: "optional chain used"
2424
related_information[1].location.uri: "file://<variable>/fixtures/linter/issue_9958/issue.ts"
25-
related_information[1].location.range: Range { start: Position { line: 11, character: 21 }, end: Position { line: 11, character: 21 } }
25+
related_information[1].location.range: Range { start: Position { line: 11, character: 18 }, end: Position { line: 11, character: 19 } }
2626
severity: Some(Error)
2727
source: Some("oxc")
2828
tags: None
29+
30+
31+
code: "None"
32+
code_description.href: "None"
33+
message: "non-null assertion made after optional chain"
34+
range: Range { start: Position { line: 11, character: 21 }, end: Position { line: 11, character: 22 } }
35+
related_information[0].message: "original diagnostic"
36+
related_information[0].location.uri: "file://<variable>/fixtures/linter/issue_9958/issue.ts"
37+
related_information[0].location.range: Range { start: Position { line: 11, character: 18 }, end: Position { line: 11, character: 19 } }
38+
severity: Some(Hint)
39+
source: Some("oxc")
40+
tags: None

Diff for: ‎crates/oxc_linter/src/rules/typescript/no_non_null_asserted_optional_chain.rs

+49-13
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ use crate::{
1212
rule::Rule,
1313
};
1414

15-
fn no_non_null_asserted_optional_chain_diagnostic(span: Span, span1: Span) -> OxcDiagnostic {
16-
OxcDiagnostic::warn("non-null assertions after an optional chain expression")
17-
.with_help("Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.")
18-
.with_labels([span, span1])
15+
fn no_non_null_asserted_optional_chain_diagnostic(
16+
chain_span: Span,
17+
assertion_span: Span,
18+
) -> OxcDiagnostic {
19+
OxcDiagnostic::warn("Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.")
20+
.with_help("Remove the non-null assertion.")
21+
.with_label(assertion_span.primary_label("non-null assertion made after optional chain"))
22+
.and_label(chain_span.label("optional chain used"))
1923
}
2024

2125
#[derive(Debug, Default, Clone)]
@@ -27,20 +31,35 @@ declare_oxc_lint!(
2731
/// Disallow non-null assertions after an optional chain expression.
2832
///
2933
/// ### Why is this bad?
30-
/// `?.` optional chain expressions provide undefined if an object is null or undefined.
31-
/// Using a `!` non-null assertion to assert the result of an `?.` optional chain expression is non-nullable is likely wrong.
3234
///
33-
/// Most of the time, either the object was not nullable and did not need the `?.` for its property lookup, or the `!` is incorrect and introducing a type safety hole.
35+
/// By design, optional chain expressions (`?.`) provide `undefined` as the expression's value, if the object being
36+
/// accessed is `null` or `undefined`, instead of throwing an error. Using a non-null assertion (`!`) to assert the
37+
/// result of an optional chain expression is contradictory and likely wrong, as it indicates the code is both expecting
38+
/// the value to be potentially `null` or `undefined` and non-null at the same time.
39+
///
40+
/// In most cases, either:
41+
/// 1. The object is not nullable and did not need the `?.` for its property lookup
42+
/// 2. The non-null assertion is incorrect and introduces a type safety hole.
43+
///
44+
/// ### Examples
45+
///
46+
/// Examples of **incorrect** code for this rule:
3447
///
35-
/// ### Example
3648
/// ```ts
3749
/// foo?.bar!;
3850
/// foo?.bar()!;
3951
/// ```
52+
///
53+
/// Examples of **correct** code for this rule:
54+
///
55+
/// ```ts
56+
/// foo?.bar;
57+
/// foo.bar!;
58+
/// ```
4059
NoNonNullAssertedOptionalChain,
4160
typescript,
4261
correctness,
43-
pending
62+
fix
4463
);
4564

4665
impl Rule for NoNonNullAssertedOptionalChain {
@@ -90,10 +109,14 @@ impl Rule for NoNonNullAssertedOptionalChain {
90109
if let Some(chain_span) = chain_span {
91110
let chain_span_end = chain_span.end;
92111
let non_null_end = non_null_expr.span.end - 1;
93-
ctx.diagnostic(no_non_null_asserted_optional_chain_diagnostic(
94-
Span::new(chain_span_end, chain_span_end),
95-
Span::new(non_null_end, non_null_end),
96-
));
112+
let diagnostic = no_non_null_asserted_optional_chain_diagnostic(
113+
Span::sized(chain_span_end, 1),
114+
Span::sized(non_null_end, 1),
115+
);
116+
// ctx.diagnostic(diagnostic);
117+
ctx.diagnostic_with_fix(diagnostic, |fixer| {
118+
fixer.delete_range(Span::sized(non_null_end, 1))
119+
});
97120
}
98121
}
99122

@@ -143,11 +166,24 @@ fn test() {
143166
"(foo?.bar!)()",
144167
];
145168

169+
let fix = vec![
170+
("foo?.bar!", "foo?.bar"),
171+
("foo?.['bar']!", "foo?.['bar']"),
172+
("foo?.bar()!", "foo?.bar()"),
173+
("(foo?.bar)!.baz", "(foo?.bar).baz"),
174+
("(foo?.bar)!().baz", "(foo?.bar)().baz"),
175+
("(foo?.bar)!", "(foo?.bar)"),
176+
("(foo?.bar)!()", "(foo?.bar)()"),
177+
("(foo?.bar!)", "(foo?.bar)"),
178+
("(foo?.bar!)()", "(foo?.bar)()"),
179+
];
180+
146181
Tester::new(
147182
NoNonNullAssertedOptionalChain::NAME,
148183
NoNonNullAssertedOptionalChain::PLUGIN,
149184
pass,
150185
fail,
151186
)
187+
.expect_fix(fix)
152188
.test_and_snapshot();
153189
}
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,92 @@
11
---
22
source: crates/oxc_linter/src/tester.rs
33
---
4-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
5-
╭─[no_non_null_asserted_optional_chain.tsx:1:4]
4+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
5+
╭─[no_non_null_asserted_optional_chain.tsx:1:9]
66
1foo?.bar!;
7-
· ▲ ▲
7+
· ┬ ┬
8+
· │ ╰── non-null assertion made after optional chain
9+
· ╰── optional chain used
810
╰────
9-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
11+
help: Remove the non-null assertion.
1012

11-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
12-
╭─[no_non_null_asserted_optional_chain.tsx:1:4]
13+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
14+
╭─[no_non_null_asserted_optional_chain.tsx:1:13]
1315
1foo?.['bar']!;
14-
· ▲ ▲
16+
· ┬ ┬
17+
· │ ╰── non-null assertion made after optional chain
18+
· ╰── optional chain used
1519
╰────
16-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
20+
help: Remove the non-null assertion.
1721

18-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
19-
╭─[no_non_null_asserted_optional_chain.tsx:1:4]
22+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
23+
╭─[no_non_null_asserted_optional_chain.tsx:1:11]
2024
1foo?.bar()!;
21-
· ▲ ▲
25+
· ┬ ┬
26+
· │ ╰── non-null assertion made after optional chain
27+
· ╰── optional chain used
2228
╰────
23-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
29+
help: Remove the non-null assertion.
2430

25-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
26-
╭─[no_non_null_asserted_optional_chain.tsx:1:8]
31+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
32+
╭─[no_non_null_asserted_optional_chain.tsx:1:12]
2733
1foo.bar?.()!;
28-
· ▲ ▲
34+
· ┬ ┬
35+
· │ ╰── non-null assertion made after optional chain
36+
· ╰── optional chain used
2937
╰────
30-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
38+
help: Remove the non-null assertion.
3139

32-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
33-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
40+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
41+
╭─[no_non_null_asserted_optional_chain.tsx:1:11]
3442
1 │ (foo?.bar)!.baz
35-
· ▲ ▲
43+
· ┬ ┬
44+
· │ ╰── non-null assertion made after optional chain
45+
· ╰── optional chain used
3646
╰────
37-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
47+
help: Remove the non-null assertion.
3848

39-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
40-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
49+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
50+
╭─[no_non_null_asserted_optional_chain.tsx:1:11]
4151
1 │ (foo?.bar)!().baz
42-
· ▲ ▲
52+
· ┬ ┬
53+
· │ ╰── non-null assertion made after optional chain
54+
· ╰── optional chain used
4355
╰────
44-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
56+
help: Remove the non-null assertion.
4557

46-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
47-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
58+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
59+
╭─[no_non_null_asserted_optional_chain.tsx:1:11]
4860
1 │ (foo?.bar)!
49-
· ▲ ▲
61+
· ┬ ┬
62+
· │ ╰── non-null assertion made after optional chain
63+
· ╰── optional chain used
5064
╰────
51-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
65+
help: Remove the non-null assertion.
5266

53-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
54-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
67+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
68+
╭─[no_non_null_asserted_optional_chain.tsx:1:11]
5569
1 │ (foo?.bar)!()
56-
· ▲ ▲
70+
· ┬ ┬
71+
· │ ╰── non-null assertion made after optional chain
72+
· ╰── optional chain used
5773
╰────
58-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
74+
help: Remove the non-null assertion.
5975

60-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
61-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
76+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
77+
╭─[no_non_null_asserted_optional_chain.tsx:1:10]
6278
1 │ (foo?.bar!)
63-
· ▲ ▲
79+
· ┬ ┬
80+
· │ ╰── non-null assertion made after optional chain
81+
· ╰── optional chain used
6482
╰────
65-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
83+
help: Remove the non-null assertion.
6684

67-
typescript-eslint(no-non-null-asserted-optional-chain): non-null assertions after an optional chain expression
68-
╭─[no_non_null_asserted_optional_chain.tsx:1:5]
85+
typescript-eslint(no-non-null-asserted-optional-chain): Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong.
86+
╭─[no_non_null_asserted_optional_chain.tsx:1:10]
6987
1 │ (foo?.bar!)()
70-
· ▲ ▲
88+
· ┬ ┬
89+
· │ ╰── non-null assertion made after optional chain
90+
· ╰── optional chain used
7191
╰────
72-
help: Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong. You should remove the non-null assertion.
92+
help: Remove the non-null assertion.

0 commit comments

Comments
 (0)
Please sign in to comment.