Skip to content

💅 noUnnecessaryConditions: new RegExp(...).exec() is treated as non-nullable #11278

Description

@shbernal

Environment information

Details
CLI:
  Version:                      2.5.7
  Color support:                false

Platform:
  CPU Architecture:             x86_64
  OS:                           windows

Environment:
  BIOME_DISTRIBUTION:                npm
  BIOME_LOG_PATH:                    unset
  BIOME_LOG_PREFIX_NAME:             unset
  BIOME_LOG_LEVEL:                   unset
  BIOME_LOG_KIND:                    unset
  BIOME_CONFIG_PATH:                 unset
  BIOME_THREADS:                     unset
  BIOME_WATCHER_KIND:                unset
  BIOME_WATCHER_POLLING_INTERVAL:    unset
  NO_COLOR:                     1
  TERM:                         xterm-256color
  JS_RUNTIME_VERSION:           v24.16.0
  JS_RUNTIME_NAME:              node
  NODE_PACKAGE_MANAGER:         unset

Biome Configuration:
  Status:                       Loaded successfully.
  Path:                         biome.json
  Formatter enabled:            true
  Linter enabled:               true
  Assist enabled:               true
  VCS enabled:                  false
  HTML full support enabled:    unset

Linter:
  JavaScript enabled:           unset
  JSON enabled:                 unset
  CSS enabled:                  unset
  GraphQL enabled:              unset
  Recommended:                  unset
  Enabled rules:
    suspicious/noUnnecessaryConditions

Workspace:
  Open Documents:               0

Rule name

suspicious/noUnnecessaryConditions

Playground link

https://biomejs.dev/playground/?lintRules=all&tab=diagnostics&code=LwAvACAAYABSAGUAZwBFAHgAcAAuAHAAcgBvAHQAbwB0AHkAcABlAC4AZQB4AGUAYwBgACAAcgBlAHQAdQByAG4AcwAgAGAAUgBlAGcARQB4AHAARQB4AGUAYwBBAHIAcgBhAHkAIAB8ACAAbgB1AGwAbABgACwAIABzAG8AIABgAD8ALgBgACAAaQBzACAAcgBlAHEAdQBpAHIAZQBkAC4ACgBlAHgAcABvAHIAdAAgAGYAdQBuAGMAdABpAG8AbgAgAGMAYQBwAHQAdQByAGUAKABpAG4AcAB1AHQAOgAgAHMAdAByAGkAbgBnACkAOgAgAHMAdAByAGkAbgBnACAAfAAgAHUAbgBkAGUAZgBpAG4AZQBkACAAewAKACAAIAByAGUAdAB1AHIAbgAgAC8AYQAoAGIAKQAvAC4AZQB4AGUAYwAoAGkAbgBwAHUAdAApAD8ALgBbADEAXQA7AAoAfQAKAAoAZQB4AHAAbwByAHQAIABmAHUAbgBjAHQAaQBvAG4AIABjAGEAcAB0AHUAcgBlAEQAeQBuAGEAbQBpAGMAKABpAG4AcAB1AHQAOgAgAHMAdAByAGkAbgBnACwAIAB0AGEAZwA6ACAAcwB0AHIAaQBuAGcAKQA6ACAAcwB0AHIAaQBuAGcAIAB7AAoAIAAgAHIAZQB0AHUAcgBuACAAbgBlAHcAIABSAGUAZwBFAHgAcAAoAGAAPAAkAHsAdABhAGcAfQA%2BACgAWwBeADwAXQAqACkAYAApAC4AZQB4AGUAYwAoAGkAbgBwAHUAdAApAD8ALgBbADEAXQAgAD8APwAgACcAJwA7AAoAfQAKAA%3D%3D

Expected result

No diagnostic. RegExp.prototype.exec returns RegExpExecArray | null, so the ?. is required however the RegExp was constructed.

Reproduction

// `RegExp.prototype.exec` returns `RegExpExecArray | null`, so `?.` is required.
export function capture(input: string): string | undefined {
  return /a(b)/.exec(input)?.[1];
}

export function captureDynamic(input: string, tag: string): string {
  return new RegExp(`<${tag}>([^<]*)`).exec(input)?.[1] ?? '';
}
b-regexp-exec.ts:7:10 lint/suspicious/noUnnecessaryConditions ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  × Unnecessary optional chaining.

  > 7 │   return new RegExp(`<${tag}>([^<]*)`).exec(input)?.[1] ?? '';
      │          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  i Replace ?. with ..

  i The receiver is guaranteed to be non-nullish.

Found 1 error.

The suggested fix is unsafe: taking it turns a non-match from undefined into a TypeError at runtime.

Analysis

The two functions differ only in how the pattern is built, and only the second is flagged:

  • /a(b)/.exec(input)?.[1]not flagged, correct.
  • new RegExp(...).exec(input)?.[1] — flagged.

So the regex literal carries a correctly-nullable exec while the RegExp constructor call does not — it looks like the constructed-instance path loses the | null from the return type rather than the rule mishandling the chain.

Possibly a facet of #5977 (globals resolved from hardcoded definitions rather than real lib.d.ts), but the literal/constructor asymmetry suggests it may be fixable ahead of that work.

Context

Found while trialling the type-aware rules on a TypeScript-first OOXML/xlsx library. Dynamically built tag patterns are common in its test assertions, so this is 10 of the 148 noUnnecessaryConditions diagnostics on that tree.

Code of Conduct

  • I agree to follow Biome's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions