refactor: remove world smuggling — type styles, fragments, topologySubjectIds

registry.ts: Add optional styles and fragments table types to the
  solver's GeometryWorld. Previously these were attached via
  (world as any).styles / .fragments, bypassing the type system.

extraction.ts:
  - remapTopologyIds: accept topologySubjectIds as explicit parameter
    instead of reading (world as any)._topologySubjectIds and deleting
    it post-remap. Eliminates 3 smuggling accesses (read, set, delete)
  - All worldAny.styles / worldAny.fragments / worldAny.strings /
    worldAny.transforms / worldAny.matrices assignments now use
    typed world.xxx access. worldAny variable removed entirely.
  - attachMeasuredChWidths: use world.styles directly.
  - (result.world as any).env mutation replaced with typed
    result.world.env assignment.
  - (sizeWorld as any).styles replaced with typed access.

predicates.ts: Replace all 3 (world as any).styles inline type-assert
  reads (getSubjectFontSizePx, getRootFontSizePx, getSubjectChWidthPx)
  with direct world.styles?.xxx access. No runtime behavior change.

Zero (world as any) casts remain in extraction.ts or predicates.ts.
658 tests pass.
This commit is contained in:
John Dvorak
2026-05-22 14:38:37 -07:00
parent 45b5575e53
commit e78ffe3419
3 changed files with 59 additions and 52 deletions
+22 -36
View File
@@ -342,12 +342,7 @@ export function attachMeasuredChWidths(
selectorToIds: Map<string, number[]>,
chWidthsBySelector: Map<string, number[]>,
): void {
const worldAny = world as any
const styles = worldAny.styles as {
subjectId?: number[]
fontSize?: number[]
chWidth?: number[]
} | undefined
const styles = world.styles
if (!styles) return
const bySubject = new Map<number, number>()
for (const [key, ids] of selectorToIds) {
@@ -543,7 +538,6 @@ export async function extractWorldFastGeometry(
}) as FastExtractedPayload
const world = buildGeometryWorld(extracted.elements) as GeometryWorld
const worldAny = world as any
if (requiredFacts?.fragments) {
const fragmentId: number[] = []
@@ -581,20 +575,15 @@ export async function extractWorldFastGeometry(
}
}
worldAny.fragments = {
fragmentId,
world.fragments = {
subjectId,
boxId: fragmentId,
fragmentKind,
boxLeft,
boxTop,
boxRight,
boxBottom,
lineIndex,
flowIndex,
parentFragmentId,
firstTextRunId: [],
textRunCount: [],
}
worldAny.subjects.firstFragmentId = firstFragmentIds
worldAny.subjects.fragmentCount = fragmentCounts
world.subjects.firstFragmentId = firstFragmentIds
world.subjects.fragmentCount = fragmentCounts
}
if (requiredFacts?.geometry) {
@@ -619,7 +608,7 @@ export async function extractWorldFastGeometry(
originY.push(t.originY)
}
worldAny.transforms = {
world.transforms = {
transformId,
subjectId: transformSubjectId,
matrixStart,
@@ -627,11 +616,11 @@ export async function extractWorldFastGeometry(
originX,
originY,
}
worldAny.matrices = { values: matrices }
world.matrices = { values: matrices }
}
if (requiredFacts?.styles) {
const strings = worldAny.strings?.values ?? []
const strings = world.strings?.values ?? []
const stringToId = new Map<string, number>()
for (let i = 0; i < strings.length; i++) stringToId.set(strings[i], i)
const intern = (value: string): number => {
@@ -696,8 +685,8 @@ export async function extractWorldFastGeometry(
chWidth.push(s.chWidth)
}
worldAny.strings = { values: strings }
worldAny.styles = {
world.strings = { values: strings }
world.styles = {
subjectId: styleSubjectId,
display,
position,
@@ -757,7 +746,7 @@ export async function extractWorldFastGeometry(
// CDP Extraction
// ---------------------------------------------------------------------------
function remapTopologyIds(world: GeometryWorld): void {
function remapTopologyIds(world: GeometryWorld, topologySubjectIds?: number[]): void {
const nSubjects = world.subjects.ids.length
if (nSubjects === 0) return
@@ -766,7 +755,7 @@ function remapTopologyIds(world: GeometryWorld): void {
backendToSolver.set(world.subjects.domNodeId[i], world.subjects.ids[i])
}
const rawSubjectIds = (world as any)._topologySubjectIds as number[] | undefined
const rawSubjectIds = topologySubjectIds
function reorderAndRemap(rawValues: ArrayLike<number>, targetLength: number): number[] {
if (!rawSubjectIds || rawSubjectIds.length === 0) {
@@ -810,8 +799,6 @@ function remapTopologyIds(world: GeometryWorld): void {
if (world.scroll) {
world.scroll.containerId = remapSimple(world.scroll.containerId)
}
delete (world as any)._topologySubjectIds
}
export async function extractWorldCdp(
@@ -901,7 +888,8 @@ export async function extractWorldCdp(
const snapshot = cdpResponse.snapshots[0]
const canonical = adaptSnapshotToCanonical(snapshot)
const world = adaptCanonicalWorldToSolver(canonical as any) as GeometryWorld
;(world as any).styles = {
const topologySubjectIds = (canonical as any).topology?.subjectIds as number[] | undefined
world.styles = {
subjectId: Array.from(canonical.styles.subjectId),
lineHeight: Array.from(canonical.styles.lineHeight),
fontFamilyStringId: Array.from(canonical.styles.fontFamilyStringId),
@@ -909,9 +897,7 @@ export async function extractWorldCdp(
fontWeight: Array.from(canonical.styles.fontWeight),
}
;(world as any)._topologySubjectIds = (canonical as any).topology?.subjectIds
remapTopologyIds(world)
remapTopologyIds(world, topologySubjectIds)
const selectorToIds = new Map<string, number[]>()
for (const [selectorKey, nodeIds] of selectorToNodeIds) {
@@ -1010,10 +996,10 @@ export async function extractWorld(
? await extractWorldFastGeometry(playwrightPage, filteredSelectors, requiredFacts)
: await extractWorldCdp(playwrightPage, filteredSelectors, requiredFacts)
;(result.world as any).env = {
...(result.world as any).env,
viewportWidth: env.viewportWidth,
viewportHeight: env.viewportHeight,
result.world.env = {
...result.world.env,
viewportWidth: env.viewportWidth as number,
viewportHeight: env.viewportHeight as number,
}
if (requiredFacts?.styles) {
@@ -1959,7 +1945,7 @@ export function buildCompatibilityReport(ui: ImhotepUi): CompatibilityReport {
viewportWidth: 1280,
viewportHeight: 720,
}
;(sizeWorld as any).styles = {
sizeWorld.styles = {
subjectId: [1],
fontSize: [16],
chWidth: [9],
+10 -16
View File
@@ -314,8 +314,7 @@ function parseLengthOption(raw: unknown): { value: number; unit: string } | null
}
function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number {
const styles = (world as any).styles as { fontSize?: ArrayLike<number> } | undefined;
const fontSize = styles?.fontSize;
const fontSize = world.styles?.fontSize;
if (!fontSize) return 16;
const ids = world.subjects?.ids;
if (!ids) return 16;
@@ -326,28 +325,23 @@ function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number {
}
function getRootFontSizePx(world: GeometryWorld): number {
const styles = (world as any).styles as { fontSize?: ArrayLike<number> } | undefined;
const fontSize = styles?.fontSize;
const fontSize = world.styles?.fontSize;
if (!fontSize || fontSize.length === 0) return 16;
const fs = Number(fontSize[0] ?? 16);
return Number.isFinite(fs) && fs > 0 ? fs : 16;
}
function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number {
const styles = (world as any).styles as {
subjectId?: ArrayLike<number>
chWidth?: ArrayLike<number>
} | undefined;
const chWidth = styles?.chWidth;
if (!chWidth || chWidth.length === 0) {
const subjectIdArr = world.styles?.subjectId;
const chWidthArr = world.styles?.chWidth;
if (!chWidthArr || chWidthArr.length === 0) {
return getSubjectFontSizePx(world, subjectId) * 0.5;
}
const styleSubjectIds = styles?.subjectId;
if (styleSubjectIds && styleSubjectIds.length > 0) {
for (let i = 0; i < styleSubjectIds.length; i++) {
if (Number(styleSubjectIds[i]) !== subjectId) continue;
const w = Number(chWidth[i]);
if (subjectIdArr && subjectIdArr.length > 0) {
for (let i = 0; i < subjectIdArr.length; i++) {
if (Number(subjectIdArr[i]) !== subjectId) continue;
const w = Number(chWidthArr[i]);
if (Number.isFinite(w) && w > 0) return w;
break;
}
@@ -357,7 +351,7 @@ function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number {
if (ids) {
const idx = ids.indexOf(subjectId);
if (idx >= 0) {
const w = Number(chWidth[idx]);
const w = Number(chWidthArr[idx]);
if (Number.isFinite(w) && w > 0) return w;
}
}
+27
View File
@@ -139,6 +139,33 @@ export interface GeometryWorld {
visibleArea: number[];
clippedArea: number[];
};
/** Computed style data attached by extraction (optional, populated when requiredFacts.styles=true). */
styles?: {
subjectId: number[];
display?: number[];
position?: number[];
zIndexKind?: number[];
zIndexValue?: number[];
overflowX?: number[];
overflowY?: number[];
opacity?: number[];
visibility?: number[];
containFlags?: number[];
pointerEvents?: number[];
lineHeight?: number[];
fontFamilyStringId?: number[];
fontSize?: number[];
fontWeight?: number[];
chWidth?: number[];
};
/** Fragment data attached by extraction (optional, populated when requiredFacts.fragments=true). */
fragments?: {
subjectId: number[];
boxId: number[];
fragmentKind: number[];
firstTextRunId: number[];
textRunCount: number[];
};
}
// --- Clause descriptor -------------------------------------------------------