You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking remaining work for the noMisleadingReturnType nursery rule (#9799).
Opened per review feedback.
Rule-level improvements
Bare return / fall-through path detection - currently uses a conservative bail when the annotation includes undefined/void. This overlaps with type narrowing(📎 Implement type narrowing #8333); we should build on that infrastructure rather than implementing ad-hoc CFA in this rule.
Property-level as const in object literals - { a: "x" as const } and similar aren't detected because TypedService resolves the literal leaf to Unknown. The fix belongs in biome_js_type_info rather than ad-hoc AST walking in this rule.
Tuple element widening - is_nonunion_wider has no (Tuple, Tuple) arm, so [string, number] vs ["hello", 42] isn't detected.
Inferred union handling - when the annotation is non-union but the inferred type is a union (e.g. ternary b ? "a" : "b" infers "a" | "b"), is_wider_than doesn't decompose it.
Single-return bail-out for union annotations - the bail at line 254 skips when returns.len() == 1 && !has_any_const_return && is_literal_of_primitive, but this fires even when the annotation is a union like string | null. A function returning only "hello" with annotation string | null should be flagged since null is never returned. <no_misleading_return_type.rs>#L254
Known false positives
Short-circuit expressions with boolean operands - the type resolver leaves &&/|| results as Literal(false) | boolean instead of collapsing to boolean (TypeScript's canonical form), so the rule sees a union that looks wider than the annotation. Also affects ||, relational operators wrapped in &&/||, typeof x === "..." && ..., and instanceof-guarded chains. Sandbox
Mutable binding with reassignment - the type resolver wraps let bindings in a single-variant union containing only the initializer literal, ignoring reassignments. let n = 0; n = getNum(); return n; with : number is incorrectly flagged. Sandbox
Getter with matching setter - when a getter has a matching setter, TypeScript infers the getter's return type from the setter's parameter type instead of the body. The rule does not account for the setter and flags the getter based on its body literals alone.
Nested object literal widening (depth > 1) - is_only_property_literal_widening only checks depth 1, so { inner: { flag: true } } vs { inner: { flag: boolean } } is flagged despite contextual widening. Sandbox
Union annotation with exhausted boolean variant - is_union_wider_than_returns reports the boolean variant as wider as soon as any return is true or false, without checking whether both are present. : boolean | null returning true, false, null is incorrectly flagged. Sandbox<no_misleading_return_type.rs>#L1104
Ideas for extending coverage
These are currently limited by type infra, with some possible directions:
Class methods, object methods, getters: could be supported if TypedService exposed a method-level type query, similar to type_of_function but for class/object members. <typed.rs>#L44
Partial<T> / Required<T> / Readonly<T>: adding is_optional / is_readonly to TypeMember would allow synthesizing these utility types. <type_data.rs>#L923
Pick<T,K> / Omit<T,K>: could follow the same synthesis pattern used for Record<K,V>. <resolver.rs>#L783
object keyword annotation: is_nonunion_wider has no arm for TypeData::ObjectKeyword vs concrete object shapes (Object, InstanceOf, Tuple, Function, RegExp), so : object wider than { retry: boolean } / [1, 2, 3] / /foo/ / function values isn't detected. <no_misleading_return_type.rs>#L943 <type_data.rs>#L152
Built-in global class instances under : object: new Date() / new Map() / new Set() / new WeakMap() / new Error() aren't detected because biome_js_type_info/src/globals_ids.rs doesn't register these classes (Array / Promise / RegExp / Symbol are registered). <globals_ids.rs>
These are just my initial thoughts - suggestions and alternative approaches are very welcome. I'd love to keep working on these over time, but would also be really grateful if anyone wants to pick any of them up.
Description
Tracking remaining work for the
noMisleadingReturnTypenursery rule (#9799).Opened per review feedback.
Rule-level improvements
undefined/void. This overlaps with type narrowing(📎 Implement type narrowing #8333); we should build on that infrastructure rather than implementing ad-hoc CFA in this rule.as constin object literals -{ a: "x" as const }and similar aren't detected because TypedService resolves the literal leaf toUnknown. The fix belongs inbiome_js_type_inforather than ad-hoc AST walking in this rule.is_nonunion_widerhas no(Tuple, Tuple)arm, so[string, number]vs["hello", 42]isn't detected.b ? "a" : "b"infers"a" | "b"),is_wider_thandoesn't decompose it.returns.len() == 1 && !has_any_const_return && is_literal_of_primitive, but this fires even when the annotation is a union likestring | null. A function returning only"hello"with annotationstring | nullshould be flagged sincenullis never returned.<no_misleading_return_type.rs>#L254Known false positives
&&/||results asLiteral(false) | booleaninstead of collapsing toboolean(TypeScript's canonical form), so the rule sees a union that looks wider than the annotation. Also affects||, relational operators wrapped in&&/||,typeof x === "..." && ..., andinstanceof-guarded chains. Sandboxletbindings in a single-variant union containing only the initializer literal, ignoring reassignments.let n = 0; n = getNum(); return n;with: numberis incorrectly flagged. Sandboxis_only_property_literal_wideningonly checks depth 1, so{ inner: { flag: true } }vs{ inner: { flag: boolean } }is flagged despite contextual widening. Sandboxbooleanvariant -is_union_wider_than_returnsreports thebooleanvariant as wider as soon as any return istrueorfalse, without checking whether both are present.: boolean | nullreturningtrue,false,nullis incorrectly flagged. Sandbox<no_misleading_return_type.rs>#L1104Ideas for extending coverage
These are currently limited by type infra, with some possible directions:
Class methods, object methods, getters: could be supported if
TypedServiceexposed a method-level type query, similar totype_of_functionbut for class/object members.<typed.rs>#L44Mapped types: the
feat/mapped-types-inferencebranch has initial work that would unblock this.Partial<T>/Required<T>/Readonly<T>: addingis_optional/is_readonlytoTypeMemberwould allow synthesizing these utility types.<type_data.rs>#L923Pick<T,K>/Omit<T,K>: could follow the same synthesis pattern used forRecord<K,V>.<resolver.rs>#L783objectkeyword annotation:is_nonunion_widerhas no arm forTypeData::ObjectKeywordvs concrete object shapes (Object,InstanceOf,Tuple,Function,RegExp), so: objectwider than{ retry: boolean }/[1, 2, 3]//foo// function values isn't detected.<no_misleading_return_type.rs>#L943<type_data.rs>#L152Built-in global class instances under
: object:new Date()/new Map()/new Set()/new WeakMap()/new Error()aren't detected becausebiome_js_type_info/src/globals_ids.rsdoesn't register these classes (Array/Promise/RegExp/Symbolare registered).<globals_ids.rs>Generic type-alias unions:
type Maybe<T> = T | null; function f(): Maybe<string> { return "hello"; }isn't detected. The alias reference doesn't expand to its union body, so union detection never fires. Non-generic aliases liketype S = string | nullare inlined toTypeData::Unionby the resolver and land through fix(lint): fix false negative in noMisleadingReturnType for union annotations #10052; the generic case needs type parameter substitution inbiome_js_type_info.no_misleading_return_type.rs#L275no_misleading_return_type.rs#L307helpers.rs#L618unions.rs#L5-L38type.rs#L330-L341These are just my initial thoughts - suggestions and alternative approaches are very welcome. I'd love to keep working on these over time, but would also be really grateful if anyone wants to pick any of them up.