Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
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
22 changes: 22 additions & 0 deletions .changeset/no-extra-non-null-assertion-compound-assign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@biomejs/biome": patch
---

Fixed [#7927](https://github.com/biomejs/biome/issues/7927): [`noExtraNonNullAssertion`](https://biomejs.dev/linter/rules/no-extra-non-null-assertion) incorrectly flagged separate non-null assertions on both sides of an assignment.

The rule now correctly distinguishes between nested non-null assertions (still flagged) and separate non-null assertions on different sides of an assignment (allowed).

#### Examples

##### Valid (now allowed)

```ts
arr[0]! ^= arr[1]!;
```

##### Invalid (still flagged)

```ts
arr[0]!! ^= arr[1];
arr[0] ^= arr[1]!!;
```
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ impl Rule for NoExtraNonNullAssertion {
}
}
AnyTsNonNullAssertion::TsNonNullAssertionExpression(_) => {
// First check if this is nested within another non-null assertion (always invalid)
let is_nested_in_non_null_assertion = node
.syntax()
.ancestors()
.skip(1)
.find_map(|ancestor| {
if ancestor.kind() == JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION {
Some(true)
} else if ancestor.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
None // Continue searching
} else {
Some(false) // Found a different ancestor, stop searching
}
})
.unwrap_or(false);

if is_nested_in_non_null_assertion {
return Some(());
}

// Then check other cases
let parent = node
.syntax()
.ancestors()
Expand All @@ -90,17 +111,31 @@ impl Rule for NoExtraNonNullAssertion {
.and_then(AnyJsExpression::cast)?;

// Cases considered as invalid:
// - TsNonNullAssertionAssignment > TsNonNullAssertionExpression
// - TsNonNullAssertionExpression > TsNonNullAssertionExpression
// - TsNonNullAssertionAssignment > TsNonNullAssertionExpression (nested in left side)
// - JsCallExpression[optional] > TsNonNullAssertionExpression
// - JsStaticMemberExpression[optional] > TsNonNullAssertionExpression
let has_extra_non_assertion = match parent {
AnyJsExpression::JsAssignmentExpression(expr) => expr
.left()
.ok()?
.as_any_js_assignment()?
.as_ts_non_null_assertion_assignment()
.is_some(),
AnyJsExpression::JsAssignmentExpression(expr) => {
// Only flag if the non-null assertion is nested within the left side,
// not if it's a separate assertion on the right side
let left = expr.left().ok()?;
let left_syntax = left.syntax();
let current_syntax = node.syntax();

// Check if current node is nested within the left side
if left_syntax
.descendants()
.any(|desc| desc == *current_syntax)
{
left.as_any_js_assignment()?
.as_ts_non_null_assertion_assignment()
.is_some()
} else {
// Current node is on the right side, don't flag it
// (nested assertions on right side are already handled above)
false
}
}
AnyJsExpression::TsNonNullAssertionExpression(_) => true,
AnyJsExpression::JsStaticMemberExpression(expr) => expr.is_optional(),
AnyJsExpression::JsCallExpression(expr) => expr.is_optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ case14!! = null
if (case15!!) {}

if (!case16!!) {}

// Test cases for issue #7927: nested assertions in compound assignments should be flagged
const arr1: number[] = [1, 2, 3];
arr1[0]!! ^= arr1[1];

const arr2: number[] = [1, 2, 3];
arr2[0] ^= arr2[1]!!;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 151
expression: invalid.ts
---
# Input
Expand Down Expand Up @@ -53,6 +54,13 @@ if (case15!!) {}

if (!case16!!) {}

// Test cases for issue #7927: nested assertions in compound assignments should be flagged
const arr1: number[] = [1, 2, 3];
arr1[0]!! ^= arr1[1];

const arr2: number[] = [1, 2, 3];
arr2[0] ^= arr2[1]!!;

```

# Diagnostics
Expand Down Expand Up @@ -284,14 +292,14 @@ invalid.ts:42:1 lint/suspicious/noExtraNonNullAssertion FIXABLE ━━━━
40 │ case12!?.[a.b!!];
41 │
> 42 │ case13!!! = null
│ ^^^^^^^^
│ ^^^^^^^
43 │
44 │ case14!! = null

i Safe fix: Remove extra non-null assertion.

42 │ case13!!!·=·null
-
-

```

Expand All @@ -303,14 +311,14 @@ invalid.ts:42:1 lint/suspicious/noExtraNonNullAssertion FIXABLE ━━━━
40 │ case12!?.[a.b!!];
41 │
> 42 │ case13!!! = null
│ ^^^^^^^
│ ^^^^^^^^
43 │
44 │ case14!! = null

i Safe fix: Remove extra non-null assertion.

42 │ case13!!!·=·null
-
-

```

Expand Down Expand Up @@ -362,10 +370,47 @@ invalid.ts:48:6 lint/suspicious/noExtraNonNullAssertion FIXABLE ━━━━
> 48 │ if (!case16!!) {}
│ ^^^^^^^
49 │
50 │ // Test cases for issue #7927: nested assertions in compound assignments should be flagged

i Safe fix: Remove extra non-null assertion.

48 │ if·(!case16!!)·{}
│ -

```

```
invalid.ts:52:1 lint/suspicious/noExtraNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Forbidden extra non-null assertion.

50 │ // Test cases for issue #7927: nested assertions in compound assignments should be flagged
51 │ const arr1: number[] = [1, 2, 3];
> 52 │ arr1[0]!! ^= arr1[1];
│ ^^^^^^^^
53 │
54 │ const arr2: number[] = [1, 2, 3];

i Safe fix: Remove extra non-null assertion.

52 │ arr1[0]!!·^=·arr1[1];
│ -

```

```
invalid.ts:55:12 lint/suspicious/noExtraNonNullAssertion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Forbidden extra non-null assertion.

54 │ const arr2: number[] = [1, 2, 3];
> 55 │ arr2[0] ^= arr2[1]!!;
│ ^^^^^^^^
56 │

i Safe fix: Remove extra non-null assertion.

55 │ arr2[0]·^=·arr2[1]!!;
│ -

```
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ function case5(key: string | null) {
function issue3419(value: string | null): string {
return (value!);
}

// Test case for issue #7927: compound assignment with non-null assertions on both sides
const arr: number[] = [1, 2, 3];
arr[0]! ^= arr[1]!;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 151
expression: valid.ts
---
# Input
Expand All @@ -26,4 +27,8 @@ function issue3419(value: string | null): string {
return (value!);
}

// Test case for issue #7927: compound assignment with non-null assertions on both sides
const arr: number[] = [1, 2, 3];
arr[0]! ^= arr[1]!;

```