biomejs/biome

๐Ÿ’… `noConfusingVoidType` shouldn't report for `void | never`

Opened this issue ยท 0 comments

Environment information

CLI:
  Version:                      1.9.5-nightly.ff02a0b
  Color support:                true

Platform:
  CPU Architecture:             x86_64
  OS:                           windows

Environment:
  BIOME_LOG_PATH:               unset
  BIOME_LOG_PREFIX_NAME:        unset
  BIOME_CONFIG_PATH:            unset
  NO_COLOR:                     unset
  TERM:                         unset
  JS_RUNTIME_VERSION:           "v22.9.0"
  JS_RUNTIME_NAME:              "node"
  NODE_PACKAGE_MANAGER:         unset

Biome Configuration:
  Status:                       Loaded successfully
  Formatter disabled:           false
  Linter disabled:              false
  Organize imports disabled:    false
  VCS disabled:                 true

Linter:
  JavaScript enabled:           true
  JSON enabled:                 true
  CSS enabled:                  true
  GraphQL enabled:              false
  Recommended:                  false
  All:                          false
  Enabled rules:
  a11y/noAccessKey
  a11y/noAriaHiddenOnFocusable
  a11y/noAriaUnsupportedElements
  a11y/noAutofocus
  a11y/noBlankTarget
  a11y/noDistractingElements
  a11y/noHeaderScope
  a11y/noInteractiveElementToNoninteractiveRole
  a11y/noLabelWithoutControl
  a11y/noNoninteractiveElementToInteractiveRole
  a11y/noNoninteractiveTabindex
  a11y/noPositiveTabindex
  a11y/noRedundantAlt
  a11y/noRedundantRoles
  a11y/noSvgWithoutTitle
  a11y/useAltText
  a11y/useAnchorContent
  a11y/useAriaActivedescendantWithTabindex
  a11y/useAriaPropsForRole
  a11y/useButtonType
  a11y/useFocusableInteractive
  a11y/useGenericFontNames
  a11y/useHeadingContent
  a11y/useHtmlLang
  a11y/useIframeTitle
  a11y/useKeyWithClickEvents
  a11y/useKeyWithMouseEvents
  a11y/useMediaCaption
  a11y/useSemanticElements
  a11y/useValidAnchor
  a11y/useValidAriaProps
  a11y/useValidAriaRole
  a11y/useValidAriaValues
  a11y/useValidLang
  complexity/noBannedTypes
  complexity/noEmptyTypeParameters
  complexity/noExcessiveNestedTestSuites
  complexity/noExtraBooleanCast
  complexity/noForEach
  complexity/noMultipleSpacesInRegularExpressionLiterals
  complexity/noStaticOnlyClass
  complexity/noThisInStatic
  complexity/noUselessCatch
  complexity/noUselessConstructor
  complexity/noUselessEmptyExport
  complexity/noUselessFragments
  complexity/noUselessLabel
  complexity/noUselessLoneBlockStatements
  complexity/noUselessRename
  complexity/noUselessSwitchCase
  complexity/noUselessTernary
  complexity/noUselessThisAlias
  complexity/noUselessTypeConstraint
  complexity/noWith
  complexity/useArrowFunction
  complexity/useFlatMap
  complexity/useLiteralKeys
  complexity/useOptionalChain
  complexity/useRegexLiterals
  complexity/useSimpleNumberKeys
  correctness/noChildrenProp
  correctness/noConstAssign
  correctness/noConstantCondition
  correctness/noConstructorReturn
  correctness/noEmptyCharacterClassInRegex
  correctness/noEmptyPattern
  correctness/noFlatMapIdentity
  correctness/noGlobalObjectCalls
  correctness/noInnerDeclarations
  correctness/noInvalidBuiltinInstantiation
  correctness/noInvalidConstructorSuper
  correctness/noInvalidDirectionInLinearGradient
  correctness/noInvalidGridAreas
  correctness/noInvalidPositionAtImportRule
  correctness/noInvalidUseBeforeDeclaration
  correctness/noNonoctalDecimalEscape
  correctness/noPrecisionLoss
  correctness/noRenderReturnValue
  correctness/noSelfAssign
  correctness/noSetterReturn
  correctness/noStringCaseMismatch
  correctness/noSwitchDeclarations
  correctness/noUnknownFunction
  correctness/noUnknownMediaFeatureName
  correctness/noUnknownProperty
  correctness/noUnknownUnit
  correctness/noUnmatchableAnbSelector
  correctness/noUnnecessaryContinue
  correctness/noUnreachable
  correctness/noUnreachableSuper
  correctness/noUnsafeFinally
  correctness/noUnsafeOptionalChaining
  correctness/noUnusedLabels
  correctness/noVoidElementsWithChildren
  correctness/noVoidTypeReturn
  correctness/useExhaustiveDependencies
  correctness/useIsNan
  correctness/useJsxKeyInIterable
  correctness/useValidForDirection
  correctness/useYield
  performance/noAccumulatingSpread
  performance/noDelete
  security/noDangerouslySetInnerHtml
  security/noDangerouslySetInnerHtmlWithChildren
  security/noGlobalEval
  style/noArguments
  style/noCommaOperator
  style/noInferrableTypes
  style/noNonNullAssertion
  style/noParameterAssign
  style/noUnusedTemplateLiteral
  style/noUselessElse
  style/noVar
  style/useAsConstAssertion
  style/useConst
  style/useDefaultParameterLast
  style/useEnumInitializers
  style/useExponentiationOperator
  style/useExportType
  style/useImportType
  style/useLiteralEnumMembers
  style/useNodejsImportProtocol
  style/useNumberNamespace
  style/useNumericLiterals
  style/useSelfClosingElements
  style/useShorthandFunctionType
  style/useSingleVarDeclarator
  style/useTemplate
  style/useWhile
  suspicious/noApproximativeNumericConstant
  suspicious/noArrayIndexKey
  suspicious/noAssignInExpressions
  suspicious/noAsyncPromiseExecutor
  suspicious/noCatchAssign
  suspicious/noClassAssign
  suspicious/noCommentText
  suspicious/noCompareNegZero
  suspicious/noConfusingLabels
  suspicious/noConfusingVoidType
  suspicious/noConstEnum
  suspicious/noControlCharactersInRegex
  suspicious/noDebugger
  suspicious/noDoubleEquals
  suspicious/noDuplicateAtImportRules
  suspicious/noDuplicateCase
  suspicious/noDuplicateClassMembers
  suspicious/noDuplicateFontNames
  suspicious/noDuplicateJsxProps
  suspicious/noDuplicateObjectKeys
  suspicious/noDuplicateParameters
  suspicious/noDuplicateSelectorsKeyframeBlock
  suspicious/noDuplicateTestHooks
  suspicious/noEmptyBlock
  suspicious/noEmptyInterface
  suspicious/noExplicitAny
  suspicious/noExportsInTest
  suspicious/noExtraNonNullAssertion
  suspicious/noFallthroughSwitchClause
  suspicious/noFocusedTests
  suspicious/noFunctionAssign
  suspicious/noGlobalAssign
  suspicious/noGlobalIsFinite
  suspicious/noGlobalIsNan
  suspicious/noImplicitAnyLet
  suspicious/noImportAssign
  suspicious/noImportantInKeyframe
  suspicious/noLabelVar
  suspicious/noMisleadingCharacterClass
  suspicious/noMisleadingInstantiator
  suspicious/noMisrefactoredShorthandAssign
  suspicious/noPrototypeBuiltins
  suspicious/noRedeclare
  suspicious/noRedundantUseStrict
  suspicious/noSelfCompare
  suspicious/noShadowRestrictedNames
  suspicious/noShorthandPropertyOverrides
  suspicious/noSparseArray
  suspicious/noSuspiciousSemicolonInJsx
  suspicious/noThenProperty
  suspicious/noUnsafeDeclarationMerging
  suspicious/noUnsafeNegation
  suspicious/useDefaultSwitchClauseLast
  suspicious/useGetterReturn
  suspicious/useIsArray
  suspicious/useNamespaceKeyword
  suspicious/useValidTypeof

Workspace:
  Open Documents:               0

Rule name

lint/suspicious/noConfusingVoidType

Playground link

https://biomejs.dev/playground/?code=ZgB1AG4AYwB0AGkAbwBuACAAdABoAHIAbwB3AEEAbgBFAHIAcgBvAHIAKAApADoAIAB2AG8AaQBkACAAfAAgAG4AZQB2AGUAcgAgAHsACgAgACAAdABoAHIAbwB3ACAAbgBlAHcAIABFAHIAcgBvAHIAKAAiAFQAaABpAHMAIABmAHUAbgBjAHQAaQBvAG4AIABtAGkAZwBoAHQAIAB0AGgAcgBvAHcAIABhAG4AIABlAHIAcgBvAHIALgAiACkACgB9AA%3D%3D

Expected result

The noConfusingVoidType rule is triggering a false positive for the following pattern:

function throwAnError(): void | never {
  throw new Error("This function might throw an error.")
}

Technical Justification

This should be considered valid TypeScript code for the following reasons:

  1. The never type in TypeScript explicitly indicates that a function will not return normally (e.g., it will throw an error or enter an infinite loop)

  2. TypeScript requires a union type with never to avoid error TS2534:

    TS2534: A function returning never cannot have a reachable end point.
    
  3. The void | never union is semantically meaningful here because:

    • void indicates the function returns nothing when successful
    • never indicates the function will throw (never return) on invalid input
  4. This pattern is commonly used in TypeScript codebases for functions that either:

    • Complete successfully returning nothing (void)
    • Throw an error (never)

Current Behavior

If we specify only never as the return type:

function throwAnError(): never {
  throw new Error("This function might throw an error.")
}

TypeScript will raise error TS2534, requiring us to use a union type. Since the function doesn't return any value (only throws on invalid input), void | never is the appropriate type union.

Proposed Resolution

The noConfusingVoidType rule should be updated to allow void | never unions specifically when:

  1. The function body contains a throw statement
  2. The never type is used to indicate error throwing behavior

This would align with TypeScript's type system design and common error handling patterns in the ecosystem.

Additional Context

This pattern is particularly useful for:

  • Input validation functions
  • Guard clauses
  • Error boundary functions
  • Type narrowing functions

The use of void | never clearly communicates to developers that they should handle potential errors using try-catch blocks, making the code more maintainable and type-safe.

Code of Conduct

  • I agree to follow Biome's Code of Conduct