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:
@@ -342,12 +342,7 @@ export function attachMeasuredChWidths(
|
|||||||
selectorToIds: Map<string, number[]>,
|
selectorToIds: Map<string, number[]>,
|
||||||
chWidthsBySelector: Map<string, number[]>,
|
chWidthsBySelector: Map<string, number[]>,
|
||||||
): void {
|
): void {
|
||||||
const worldAny = world as any
|
const styles = world.styles
|
||||||
const styles = worldAny.styles as {
|
|
||||||
subjectId?: number[]
|
|
||||||
fontSize?: number[]
|
|
||||||
chWidth?: number[]
|
|
||||||
} | undefined
|
|
||||||
if (!styles) return
|
if (!styles) return
|
||||||
const bySubject = new Map<number, number>()
|
const bySubject = new Map<number, number>()
|
||||||
for (const [key, ids] of selectorToIds) {
|
for (const [key, ids] of selectorToIds) {
|
||||||
@@ -543,7 +538,6 @@ export async function extractWorldFastGeometry(
|
|||||||
}) as FastExtractedPayload
|
}) as FastExtractedPayload
|
||||||
|
|
||||||
const world = buildGeometryWorld(extracted.elements) as GeometryWorld
|
const world = buildGeometryWorld(extracted.elements) as GeometryWorld
|
||||||
const worldAny = world as any
|
|
||||||
|
|
||||||
if (requiredFacts?.fragments) {
|
if (requiredFacts?.fragments) {
|
||||||
const fragmentId: number[] = []
|
const fragmentId: number[] = []
|
||||||
@@ -581,20 +575,15 @@ export async function extractWorldFastGeometry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worldAny.fragments = {
|
world.fragments = {
|
||||||
fragmentId,
|
|
||||||
subjectId,
|
subjectId,
|
||||||
|
boxId: fragmentId,
|
||||||
fragmentKind,
|
fragmentKind,
|
||||||
boxLeft,
|
firstTextRunId: [],
|
||||||
boxTop,
|
textRunCount: [],
|
||||||
boxRight,
|
|
||||||
boxBottom,
|
|
||||||
lineIndex,
|
|
||||||
flowIndex,
|
|
||||||
parentFragmentId,
|
|
||||||
}
|
}
|
||||||
worldAny.subjects.firstFragmentId = firstFragmentIds
|
world.subjects.firstFragmentId = firstFragmentIds
|
||||||
worldAny.subjects.fragmentCount = fragmentCounts
|
world.subjects.fragmentCount = fragmentCounts
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredFacts?.geometry) {
|
if (requiredFacts?.geometry) {
|
||||||
@@ -619,7 +608,7 @@ export async function extractWorldFastGeometry(
|
|||||||
originY.push(t.originY)
|
originY.push(t.originY)
|
||||||
}
|
}
|
||||||
|
|
||||||
worldAny.transforms = {
|
world.transforms = {
|
||||||
transformId,
|
transformId,
|
||||||
subjectId: transformSubjectId,
|
subjectId: transformSubjectId,
|
||||||
matrixStart,
|
matrixStart,
|
||||||
@@ -627,11 +616,11 @@ export async function extractWorldFastGeometry(
|
|||||||
originX,
|
originX,
|
||||||
originY,
|
originY,
|
||||||
}
|
}
|
||||||
worldAny.matrices = { values: matrices }
|
world.matrices = { values: matrices }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredFacts?.styles) {
|
if (requiredFacts?.styles) {
|
||||||
const strings = worldAny.strings?.values ?? []
|
const strings = world.strings?.values ?? []
|
||||||
const stringToId = new Map<string, number>()
|
const stringToId = new Map<string, number>()
|
||||||
for (let i = 0; i < strings.length; i++) stringToId.set(strings[i], i)
|
for (let i = 0; i < strings.length; i++) stringToId.set(strings[i], i)
|
||||||
const intern = (value: string): number => {
|
const intern = (value: string): number => {
|
||||||
@@ -696,8 +685,8 @@ export async function extractWorldFastGeometry(
|
|||||||
chWidth.push(s.chWidth)
|
chWidth.push(s.chWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
worldAny.strings = { values: strings }
|
world.strings = { values: strings }
|
||||||
worldAny.styles = {
|
world.styles = {
|
||||||
subjectId: styleSubjectId,
|
subjectId: styleSubjectId,
|
||||||
display,
|
display,
|
||||||
position,
|
position,
|
||||||
@@ -757,7 +746,7 @@ export async function extractWorldFastGeometry(
|
|||||||
// CDP Extraction
|
// CDP Extraction
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function remapTopologyIds(world: GeometryWorld): void {
|
function remapTopologyIds(world: GeometryWorld, topologySubjectIds?: number[]): void {
|
||||||
const nSubjects = world.subjects.ids.length
|
const nSubjects = world.subjects.ids.length
|
||||||
if (nSubjects === 0) return
|
if (nSubjects === 0) return
|
||||||
|
|
||||||
@@ -766,7 +755,7 @@ function remapTopologyIds(world: GeometryWorld): void {
|
|||||||
backendToSolver.set(world.subjects.domNodeId[i], world.subjects.ids[i])
|
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[] {
|
function reorderAndRemap(rawValues: ArrayLike<number>, targetLength: number): number[] {
|
||||||
if (!rawSubjectIds || rawSubjectIds.length === 0) {
|
if (!rawSubjectIds || rawSubjectIds.length === 0) {
|
||||||
@@ -810,8 +799,6 @@ function remapTopologyIds(world: GeometryWorld): void {
|
|||||||
if (world.scroll) {
|
if (world.scroll) {
|
||||||
world.scroll.containerId = remapSimple(world.scroll.containerId)
|
world.scroll.containerId = remapSimple(world.scroll.containerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete (world as any)._topologySubjectIds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractWorldCdp(
|
export async function extractWorldCdp(
|
||||||
@@ -901,7 +888,8 @@ export async function extractWorldCdp(
|
|||||||
const snapshot = cdpResponse.snapshots[0]
|
const snapshot = cdpResponse.snapshots[0]
|
||||||
const canonical = adaptSnapshotToCanonical(snapshot)
|
const canonical = adaptSnapshotToCanonical(snapshot)
|
||||||
const world = adaptCanonicalWorldToSolver(canonical as any) as GeometryWorld
|
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),
|
subjectId: Array.from(canonical.styles.subjectId),
|
||||||
lineHeight: Array.from(canonical.styles.lineHeight),
|
lineHeight: Array.from(canonical.styles.lineHeight),
|
||||||
fontFamilyStringId: Array.from(canonical.styles.fontFamilyStringId),
|
fontFamilyStringId: Array.from(canonical.styles.fontFamilyStringId),
|
||||||
@@ -909,9 +897,7 @@ export async function extractWorldCdp(
|
|||||||
fontWeight: Array.from(canonical.styles.fontWeight),
|
fontWeight: Array.from(canonical.styles.fontWeight),
|
||||||
}
|
}
|
||||||
|
|
||||||
;(world as any)._topologySubjectIds = (canonical as any).topology?.subjectIds
|
remapTopologyIds(world, topologySubjectIds)
|
||||||
|
|
||||||
remapTopologyIds(world)
|
|
||||||
|
|
||||||
const selectorToIds = new Map<string, number[]>()
|
const selectorToIds = new Map<string, number[]>()
|
||||||
for (const [selectorKey, nodeIds] of selectorToNodeIds) {
|
for (const [selectorKey, nodeIds] of selectorToNodeIds) {
|
||||||
@@ -1010,10 +996,10 @@ export async function extractWorld(
|
|||||||
? await extractWorldFastGeometry(playwrightPage, filteredSelectors, requiredFacts)
|
? await extractWorldFastGeometry(playwrightPage, filteredSelectors, requiredFacts)
|
||||||
: await extractWorldCdp(playwrightPage, filteredSelectors, requiredFacts)
|
: await extractWorldCdp(playwrightPage, filteredSelectors, requiredFacts)
|
||||||
|
|
||||||
;(result.world as any).env = {
|
result.world.env = {
|
||||||
...(result.world as any).env,
|
...result.world.env,
|
||||||
viewportWidth: env.viewportWidth,
|
viewportWidth: env.viewportWidth as number,
|
||||||
viewportHeight: env.viewportHeight,
|
viewportHeight: env.viewportHeight as number,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredFacts?.styles) {
|
if (requiredFacts?.styles) {
|
||||||
@@ -1959,7 +1945,7 @@ export function buildCompatibilityReport(ui: ImhotepUi): CompatibilityReport {
|
|||||||
viewportWidth: 1280,
|
viewportWidth: 1280,
|
||||||
viewportHeight: 720,
|
viewportHeight: 720,
|
||||||
}
|
}
|
||||||
;(sizeWorld as any).styles = {
|
sizeWorld.styles = {
|
||||||
subjectId: [1],
|
subjectId: [1],
|
||||||
fontSize: [16],
|
fontSize: [16],
|
||||||
chWidth: [9],
|
chWidth: [9],
|
||||||
|
|||||||
@@ -314,8 +314,7 @@ function parseLengthOption(raw: unknown): { value: number; unit: string } | null
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number {
|
function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number {
|
||||||
const styles = (world as any).styles as { fontSize?: ArrayLike<number> } | undefined;
|
const fontSize = world.styles?.fontSize;
|
||||||
const fontSize = styles?.fontSize;
|
|
||||||
if (!fontSize) return 16;
|
if (!fontSize) return 16;
|
||||||
const ids = world.subjects?.ids;
|
const ids = world.subjects?.ids;
|
||||||
if (!ids) return 16;
|
if (!ids) return 16;
|
||||||
@@ -326,28 +325,23 @@ function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRootFontSizePx(world: GeometryWorld): number {
|
function getRootFontSizePx(world: GeometryWorld): number {
|
||||||
const styles = (world as any).styles as { fontSize?: ArrayLike<number> } | undefined;
|
const fontSize = world.styles?.fontSize;
|
||||||
const fontSize = styles?.fontSize;
|
|
||||||
if (!fontSize || fontSize.length === 0) return 16;
|
if (!fontSize || fontSize.length === 0) return 16;
|
||||||
const fs = Number(fontSize[0] ?? 16);
|
const fs = Number(fontSize[0] ?? 16);
|
||||||
return Number.isFinite(fs) && fs > 0 ? fs : 16;
|
return Number.isFinite(fs) && fs > 0 ? fs : 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number {
|
function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number {
|
||||||
const styles = (world as any).styles as {
|
const subjectIdArr = world.styles?.subjectId;
|
||||||
subjectId?: ArrayLike<number>
|
const chWidthArr = world.styles?.chWidth;
|
||||||
chWidth?: ArrayLike<number>
|
if (!chWidthArr || chWidthArr.length === 0) {
|
||||||
} | undefined;
|
|
||||||
const chWidth = styles?.chWidth;
|
|
||||||
if (!chWidth || chWidth.length === 0) {
|
|
||||||
return getSubjectFontSizePx(world, subjectId) * 0.5;
|
return getSubjectFontSizePx(world, subjectId) * 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleSubjectIds = styles?.subjectId;
|
if (subjectIdArr && subjectIdArr.length > 0) {
|
||||||
if (styleSubjectIds && styleSubjectIds.length > 0) {
|
for (let i = 0; i < subjectIdArr.length; i++) {
|
||||||
for (let i = 0; i < styleSubjectIds.length; i++) {
|
if (Number(subjectIdArr[i]) !== subjectId) continue;
|
||||||
if (Number(styleSubjectIds[i]) !== subjectId) continue;
|
const w = Number(chWidthArr[i]);
|
||||||
const w = Number(chWidth[i]);
|
|
||||||
if (Number.isFinite(w) && w > 0) return w;
|
if (Number.isFinite(w) && w > 0) return w;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -357,7 +351,7 @@ function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number {
|
|||||||
if (ids) {
|
if (ids) {
|
||||||
const idx = ids.indexOf(subjectId);
|
const idx = ids.indexOf(subjectId);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const w = Number(chWidth[idx]);
|
const w = Number(chWidthArr[idx]);
|
||||||
if (Number.isFinite(w) && w > 0) return w;
|
if (Number.isFinite(w) && w > 0) return w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,33 @@ export interface GeometryWorld {
|
|||||||
visibleArea: number[];
|
visibleArea: number[];
|
||||||
clippedArea: 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 -------------------------------------------------------
|
// --- Clause descriptor -------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user