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
Environment information
Details
Rule name
suspicious/noUnnecessaryConditionsPlayground link
https://biomejs.dev/playground/?lintRules=all&tab=diagnostics&code=LwAvACAAYABSAGUAZwBFAHgAcAAuAHAAcgBvAHQAbwB0AHkAcABlAC4AZQB4AGUAYwBgACAAcgBlAHQAdQByAG4AcwAgAGAAUgBlAGcARQB4AHAARQB4AGUAYwBBAHIAcgBhAHkAIAB8ACAAbgB1AGwAbABgACwAIABzAG8AIABgAD8ALgBgACAAaQBzACAAcgBlAHEAdQBpAHIAZQBkAC4ACgBlAHgAcABvAHIAdAAgAGYAdQBuAGMAdABpAG8AbgAgAGMAYQBwAHQAdQByAGUAKABpAG4AcAB1AHQAOgAgAHMAdAByAGkAbgBnACkAOgAgAHMAdAByAGkAbgBnACAAfAAgAHUAbgBkAGUAZgBpAG4AZQBkACAAewAKACAAIAByAGUAdAB1AHIAbgAgAC8AYQAoAGIAKQAvAC4AZQB4AGUAYwAoAGkAbgBwAHUAdAApAD8ALgBbADEAXQA7AAoAfQAKAAoAZQB4AHAAbwByAHQAIABmAHUAbgBjAHQAaQBvAG4AIABjAGEAcAB0AHUAcgBlAEQAeQBuAGEAbQBpAGMAKABpAG4AcAB1AHQAOgAgAHMAdAByAGkAbgBnACwAIAB0AGEAZwA6ACAAcwB0AHIAaQBuAGcAKQA6ACAAcwB0AHIAaQBuAGcAIAB7AAoAIAAgAHIAZQB0AHUAcgBuACAAbgBlAHcAIABSAGUAZwBFAHgAcAAoAGAAPAAkAHsAdABhAGcAfQA%2BACgAWwBeADwAXQAqACkAYAApAC4AZQB4AGUAYwAoAGkAbgBwAHUAdAApAD8ALgBbADEAXQAgAD8APwAgACcAJwA7AAoAfQAKAA%3D%3D
Expected result
No diagnostic.
RegExp.prototype.execreturnsRegExpExecArray | null, so the?.is required however theRegExpwas constructed.Reproduction
The suggested fix is unsafe: taking it turns a non-match from
undefinedinto aTypeErrorat 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
execwhile theRegExpconstructor call does not — it looks like the constructed-instance path loses the| nullfrom 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
noUnnecessaryConditionsdiagnostics on that tree.Code of Conduct