diff --git a/extension/eslint.config.js b/extension/eslint.config.js index 30497a4..280eb21 100644 --- a/extension/eslint.config.js +++ b/extension/eslint.config.js @@ -252,11 +252,18 @@ export default tseslint.config( // (readdir results, computed diffs) get a `localeCompare` comparator. // no-incorrect-query-selector — a deliberate `querySelectorAll("#id")` // used to assert the element count. + // prefer-number-coercion — `Number()` replaces `parseInt(x, 10)` / + // `parseFloat(x)` only where the input is a bare numeric token (a + // `\d`-anchored regex capture, `String(i)`); the two scoped + // disables in `hidden-text-strip.ts` are where `parseFloat`'s + // leading-numeric parse is load-bearing: unit-suffixed CSS + // durations (`"0.5s"`) and a possibly-`""` computed `opacity`, + // both of which `Number()` would mis-coerce. + "unicorn/prefer-number-coercion": "error", // Warn — ratchet to error in #279: "unicorn/prefer-scoped-selector": "warn", "unicorn/no-break-in-nested-loop": "warn", - "unicorn/prefer-number-coercion": "warn", // no-global-object-property-assignment stays at its recommended `error` // for production code; it's disabled only for tests (which legitimately // assign globals to set up mocks) in the test-files block below. diff --git a/extension/src/lib/__tests__/text-walk-shadow.property.test.ts b/extension/src/lib/__tests__/text-walk-shadow.property.test.ts index 2ea13e0..373c0d4 100644 --- a/extension/src/lib/__tests__/text-walk-shadow.property.test.ts +++ b/extension/src/lib/__tests__/text-walk-shadow.property.test.ts @@ -295,10 +295,7 @@ describe("walkTextNodes — shadow coverage", () => { // inside a closed wrapper, every nested open shadow is // still unreachable to the production walker, so its text // is in the exclusion set too. - const index = Number.parseInt( - (element as HTMLElement).dataset.index ?? "-1", - 10, - ); + const index = Number((element as HTMLElement).dataset.index ?? "-1"); if (index >= 0) { const shadow = built.shadowsByIndex[index]; if (shadow) { diff --git a/extension/src/rules/countdown-timer-redact.ts b/extension/src/rules/countdown-timer-redact.ts index a77fcfd..9fdd474 100644 --- a/extension/src/rules/countdown-timer-redact.ts +++ b/extension/src/rules/countdown-timer-redact.ts @@ -63,10 +63,10 @@ const UNIT_MULTIPLIERS: ReadonlyArray = [ export function parseTotalSeconds(text: string): number | null { const colon = /(? 0; } @@ -297,7 +302,7 @@ function shorthandHasNonzeroDuration(shorthand: string): boolean { DURATION_TOKEN_PATTERN.lastIndex = 0; let match: RegExpExecArray | null = DURATION_TOKEN_PATTERN.exec(shorthand); while (match !== null) { - if (Number.parseFloat(match[1] ?? "") > 0) { + if (Number(match[1] ?? "") > 0) { return true; } match = DURATION_TOKEN_PATTERN.exec(shorthand); @@ -434,7 +439,7 @@ function isCollapsedTransform(transform: string): boolean { } const matrix = /^matrix\(([^)]+)\)$/.exec(transform); if (matrix?.[1]) { - const parts = matrix[1].split(",").map((s) => Number.parseFloat(s.trim())); + const parts = matrix[1].split(",").map((s) => Number(s.trim())); if ( parts.length >= 4 && (Math.abs(parts[0] ?? 1) < 1e-6 || Math.abs(parts[3] ?? 1) < 1e-6) @@ -444,9 +449,7 @@ function isCollapsedTransform(transform: string): boolean { } const matrix3d = /^matrix3d\(([^)]+)\)$/.exec(transform); if (matrix3d?.[1]) { - const parts = matrix3d[1] - .split(",") - .map((s) => Number.parseFloat(s.trim())); + const parts = matrix3d[1].split(",").map((s) => Number(s.trim())); // matrix3d is column-major 4×4; scale-x lives at m11 (index 0), // scale-y at m22 (index 5). if ( @@ -479,6 +482,11 @@ function detectHiddenByCss( }; } if ( + // parseFloat, not Number: an unset computed `opacity` comes back as `""` + // (notably under jsdom), and `Number("") === 0` would flag every such + // element as opacity-0 and redact it. `parseFloat("")` is `NaN`, so only a + // genuine `0`/`0.0` opacity matches here. + // eslint-disable-next-line unicorn/prefer-number-coercion -- "" opacity; Number("") === 0 false-positives Number.parseFloat(style.opacity) === 0 && !hasOpacityAnimationInFlight(style) ) { @@ -651,10 +659,10 @@ function parseColorViaRegex(value: string): RGBA | null { if (!match?.[1] || !match[2] || !match[3]) { return null; } - const r = Number.parseFloat(match[1]); - const g = Number.parseFloat(match[2]); - const b = Number.parseFloat(match[3]); - const a = match[4] === undefined ? 1 : Number.parseFloat(match[4]); + const r = Number(match[1]); + const g = Number(match[2]); + const b = Number(match[3]); + const a = match[4] === undefined ? 1 : Number(match[4]); if ([r, g, b, a].some((n) => Number.isNaN(n))) { return null; }