Skip to content

Commit adbf41b

Browse files
committed
refactor: strengthen fiber type guards with runtime validation and cleanup
- Convert boolean-returning fiber tag helpers to real TS type predicates - Add stateNode !== null checks to host fiber type guards - Validate full shapes in isHookState, isEffectState, isUpdateQueue, and isMemoComponent instead of only checking key existence - Add isUpdate type guard for Update<S> shape validation - Tighten isTextProps to validate nodeValue is string or number - Tighten isFiberRoot to verify property types, not just presence - Guard isHTMLElement, isHTMLInputElement, isElement, isTextNode against missing DOM globals for SSR safety - Fix assertTextProps error message to report correct prop source - Reject prototype-polluting keys in setDynamicProperty/getDynamicProperty - Add JSDoc warnings to getMemoizedState/assertMemoizedState about unchecked casts - Rename mergeLanesUnsafe/intersectLanesUnsafe/removeLanesUnsafe to mergeLanes/intersectLanes/removeLanes - Move Host* type aliases above their corresponding type guard functions
1 parent eaebf87 commit adbf41b

File tree

1 file changed

+51
-41
lines changed

1 file changed

+51
-41
lines changed

src/fiber/typeGuards.ts

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,42 @@ import type {
2121
} from "./types";
2222
import { NoFlags, NoLanes, WorkTag } from "./types";
2323

24+
// ============================================
25+
// Narrowed Fiber Type Aliases
26+
// ============================================
27+
28+
/**
29+
* Narrowed fiber type for host components with non-null stateNode.
30+
*/
31+
export type HostComponentFiber = Fiber & {
32+
tag: typeof WorkTag.HostComponent;
33+
stateNode: Element;
34+
};
35+
36+
/**
37+
* Narrowed fiber type for host text with non-null stateNode.
38+
*/
39+
export type HostTextFiber = Fiber & {
40+
tag: typeof WorkTag.HostText;
41+
stateNode: Text;
42+
};
43+
44+
/**
45+
* Narrowed fiber type for host root with non-null stateNode.
46+
*/
47+
export type HostRootFiber = Fiber & {
48+
tag: typeof WorkTag.HostRoot;
49+
stateNode: FiberRoot;
50+
};
51+
52+
/**
53+
* Narrowed fiber type for portal with non-null stateNode.
54+
*/
55+
export type HostPortalFiber = Fiber & {
56+
tag: typeof WorkTag.HostPortal;
57+
stateNode: PortalStateNode;
58+
};
59+
2460
// ============================================
2561
// Fiber Tag Type Guards
2662
// ============================================
@@ -108,38 +144,6 @@ export function isContextConsumerFiber(
108144
// StateNode Assertions (using `asserts` for type narrowing)
109145
// ============================================
110146

111-
/**
112-
* Narrowed fiber type for host components with non-null stateNode.
113-
*/
114-
export type HostComponentFiber = Fiber & {
115-
tag: typeof WorkTag.HostComponent;
116-
stateNode: Element;
117-
};
118-
119-
/**
120-
* Narrowed fiber type for host text with non-null stateNode.
121-
*/
122-
export type HostTextFiber = Fiber & {
123-
tag: typeof WorkTag.HostText;
124-
stateNode: Text;
125-
};
126-
127-
/**
128-
* Narrowed fiber type for host root with non-null stateNode.
129-
*/
130-
export type HostRootFiber = Fiber & {
131-
tag: typeof WorkTag.HostRoot;
132-
stateNode: FiberRoot;
133-
};
134-
135-
/**
136-
* Narrowed fiber type for portal with non-null stateNode.
137-
*/
138-
export type HostPortalFiber = Fiber & {
139-
tag: typeof WorkTag.HostPortal;
140-
stateNode: PortalStateNode;
141-
};
142-
143147
/**
144148
* Asserts that a fiber is a host component with non-null stateNode.
145149
* Narrows the type in the calling scope.
@@ -295,14 +299,20 @@ export function assertHookState(fiber: Fiber): Hook {
295299
}
296300

297301
/**
298-
* Gets memoized state as a specific type, with null check.
302+
* Gets memoized state cast to a specific type, with null check.
303+
* WARNING: Does not perform runtime validation of T. This is an unchecked cast.
304+
* Use specific guards like {@link isHookState} or {@link isEffectState} when
305+
* runtime validation is required.
299306
*/
300307
export function getMemoizedState<T>(fiber: Fiber): T | null {
301308
return fiber.memoizedState as T | null;
302309
}
303310

304311
/**
305-
* Asserts memoized state is not null and returns as type T.
312+
* Asserts memoized state is not null and returns cast to type T.
313+
* WARNING: Does not perform runtime validation of T. This is an unchecked cast
314+
* that only verifies non-null. Use specific guards like {@link isHookState} or
315+
* {@link isEffectState} when runtime shape validation is required.
306316
* @throws Error if memoizedState is null
307317
*/
308318
export function assertMemoizedState<T>(fiber: Fiber): T {
@@ -407,7 +417,7 @@ export function isUpdateQueue<S>(value: unknown): value is UpdateQueue<S> {
407417
const obj = value as Record<string, unknown>;
408418
return (
409419
"pending" in obj &&
410-
(obj["pending"] === null || typeof obj["pending"] === "object") &&
420+
(obj["pending"] === null || isUpdate(obj["pending"])) &&
411421
"lanes" in obj &&
412422
typeof obj["lanes"] === "number" &&
413423
"dispatch" in obj &&
@@ -435,23 +445,23 @@ export function assertUpdateQueue<S>(hook: Hook): UpdateQueue<S> {
435445
// ============================================
436446

437447
/**
438-
* Safely performs bitwise OR on lanes.
448+
* Performs bitwise OR on branded Lane/Lanes types, returning a merged Lanes bitmask.
439449
*/
440-
export function mergeLanesUnsafe(a: Lanes | Lane, b: Lanes | Lane): Lanes {
450+
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
441451
return ((a as number) | (b as number)) as Lanes;
442452
}
443453

444454
/**
445-
* Safely performs bitwise AND on lanes.
455+
* Performs bitwise AND on branded Lanes types, returning the intersection.
446456
*/
447-
export function intersectLanesUnsafe(a: Lanes, b: Lanes): Lanes {
457+
export function intersectLanes(a: Lanes, b: Lanes): Lanes {
448458
return ((a as number) & (b as number)) as Lanes;
449459
}
450460

451461
/**
452-
* Safely performs bitwise AND NOT on lanes.
462+
* Performs bitwise AND NOT on branded Lanes types, removing subset from set.
453463
*/
454-
export function removeLanesUnsafe(set: Lanes, subset: Lanes | Lane): Lanes {
464+
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
455465
return ((set as number) & ~(subset as number)) as Lanes;
456466
}
457467

0 commit comments

Comments
 (0)