diff --git a/.changeset/functional-state-rust.md b/.changeset/functional-state-rust.md new file mode 100644 index 0000000000..79af590ef6 --- /dev/null +++ b/.changeset/functional-state-rust.md @@ -0,0 +1,7 @@ +--- +"oxlint-plugin-react-doctor": patch +"eslint-plugin-react-doctor": patch +"react-doctor": patch +--- + +Run `rerender-functional-setstate` natively in the patched Oxlint binding and avoid reporting a synchronous state update when the only other call to the same setter does not read that state. diff --git a/.changeset/tidy-oxen-compile.md b/.changeset/tidy-oxen-compile.md new file mode 100644 index 0000000000..81551d7735 --- /dev/null +++ b/.changeset/tidy-oxen-compile.md @@ -0,0 +1,5 @@ +--- +"react-doctor": patch +--- + +Add an opt-in, source-patched Oxlint binding path with native rule parity, cache isolation, provenance metadata, and reproducible cross-platform build tooling. diff --git a/.gitattributes b/.gitattributes index 1455cff2cd..7274c38197 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ packages/fuzz/corpus/react-bench-0.9.7-audit/** -text whitespace=-trailing-space packages/fuzz/corpus/dummy-threejs-v14-audit/** -text whitespace=-trailing-space +native/oxlint/*.patch -text whitespace=-trailing-space diff --git a/.github/workflows/native-oxlint.yml b/.github/workflows/native-oxlint.yml new file mode 100644 index 0000000000..47f24cdaff --- /dev/null +++ b/.github/workflows/native-oxlint.yml @@ -0,0 +1,165 @@ +name: Native Oxlint + +on: + push: + branches: [main] + paths: + - "LICENSE" + - "native/oxlint/**" + - "scripts/native/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "turbo.json" + - "packages/api/**" + - "packages/core/**" + - "packages/oxlint-plugin-react-doctor/**" + - "packages/react-doctor/**" + - "skills/**" + - ".github/workflows/native-oxlint.yml" + pull_request: + branches: [main] + paths: + - "LICENSE" + - "native/oxlint/**" + - "scripts/native/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "turbo.json" + - "packages/api/**" + - "packages/core/**" + - "packages/oxlint-plugin-react-doctor/**" + - "packages/react-doctor/**" + - "skills/**" + - ".github/workflows/native-oxlint.yml" + +permissions: + contents: read + +jobs: + verify-patch: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - run: node scripts/native/build-oxlint-binding.mjs --check-only + - run: node --test native/oxlint/npm/react-doctor-rust/tests/*.test.js + + build: + needs: verify-patch + timeout-minutes: ${{ matrix.runner == 'macos-15-intel' && 120 || 60 }} + env: + CARGO_BUILD_JOBS: 1 + CARGO_INCREMENTAL: 0 + CARGO_TARGET_DIR: ${{ github.workspace }}/target/native-oxlint + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + container: quay.io/pypa/manylinux_2_28_x86_64 + - runner: ubuntu-24.04-arm + container: quay.io/pypa/manylinux_2_28_aarch64 + - runner: macos-15-intel + - runner: macos-15 + - runner: windows-2025 + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: pnpm/action-setup@v5 + - uses: actions/setup-node@v5 + with: + node-version: "22.18.0" + cache: pnpm + - run: pnpm install --frozen-lockfile --prefer-offline + - uses: dtolnay/rust-toolchain@1.97.1 + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + with: + shared-key: native-oxlint-${{ matrix.runner }}${{ matrix.container && '-manylinux-2-28' || '' }} + - run: pnpm native:oxlint:build -- --output dist/native-oxlint + - run: pnpm --filter oxlint-plugin-react-doctor build + - run: pnpm native:oxlint:parity -- --directory dist/native-oxlint + - run: pnpm native:oxlint:scan-parity + - run: pnpm native:oxlint:project-parity + - uses: actions/upload-artifact@v7 + with: + name: native-oxlint-${{ matrix.runner }} + path: dist/native-oxlint/* + if-no-files-found: error + + package: + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: pnpm/action-setup@v5 + - uses: actions/setup-node@v5 + with: + node-version: "22.18.0" + cache: pnpm + - run: pnpm install --frozen-lockfile --prefer-offline + - run: pnpm exec turbo run build --filter=react-doctor + - uses: actions/download-artifact@v8 + with: + pattern: native-oxlint-* + path: dist/native-oxlint-all + merge-multiple: true + - run: pnpm native:rust:assemble -- --artifacts dist/native-oxlint-all + - run: pnpm native:rust:pack + - uses: actions/upload-artifact@v7 + with: + name: react-doctor-rust-packages + path: dist/react-doctor-rust-tarballs/* + if-no-files-found: error + + smoke: + needs: package + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + node-version: "20.19.0" + - runner: ubuntu-24.04 + node-version: "22.18.0" + - runner: ubuntu-24.04-arm + node-version: "22.18.0" + - runner: ubuntu-24.04 + node-version: "20.19.0" + container: quay.io/pypa/manylinux_2_28_x86_64 + - runner: ubuntu-24.04-arm + node-version: "20.19.0" + container: quay.io/pypa/manylinux_2_28_aarch64 + - runner: macos-15-intel + node-version: "22.18.0" + - runner: macos-15 + node-version: "22.18.0" + - runner: windows-2025 + node-version: "22.18.0" + runs-on: ${{ matrix.runner }} + container: ${{ matrix.container }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + package-manager-cache: false + - uses: actions/download-artifact@v8 + with: + name: react-doctor-rust-packages + path: dist/react-doctor-rust-tarballs + - run: node scripts/native/smoke-react-doctor-rust-package.mjs diff --git a/native/oxlint/README.md b/native/oxlint/README.md new file mode 100644 index 0000000000..367e4c17e3 --- /dev/null +++ b/native/oxlint/README.md @@ -0,0 +1,36 @@ +# Source-patched Oxlint + +This directory contains a patch against one exact Oxc commit. It adds a `react-doctor-native` plugin while leaving the stock Oxlint JavaScript launcher and all unported React Doctor rules unchanged. + +The native rule cohort is listed in `upstream.json`, with one Rust source file per rule in `rules/`. React Doctor enables those rules only when `REACT_DOCTOR_NATIVE_OXLINT_BINDING_PATH` points to a built `.node` binding. Without that environment variable, scans use the published Oxlint binding and JavaScript rules exactly as before. + +## Build and verify + +```sh +nr native:oxlint:verify +nr native:oxlint:check +nr native:oxlint:build +REACT_DOCTOR_NATIVE_OXLINT_BINDING_PATH=dist/native-oxlint/.node nr native:oxlint:parity +REACT_DOCTOR_NATIVE_OXLINT_BINDING_PATH=dist/native-oxlint/.node nr native:oxlint:parity --benchmark +REACT_DOCTOR_NATIVE_OXLINT_BINDING_PATH=dist/native-oxlint/.node nr native:oxlint:parity --corpus packages/fuzz/tmp/corpus-repos +``` + +Set `CARGO_BUILD_JOBS=1` and `CARGO_INCREMENTAL=0` for the compile-check and release build on memory-constrained builders. + +`native:oxlint:verify` clones the pinned tag, checks its commit, and proves the patch still applies. `native:oxlint:check` overlays every native rule, regenerates Oxc's rule registry, and compile-checks the linter. `native:oxlint:build` performs the same source assembly, compiles and loads the N-API binding, and writes the binding plus provenance and SHA-256 hashes to `dist/native-oxlint`. + +The parity check runs the JavaScript and native implementations over the same adversarial TypeScript fixture and compares normalized diagnostics. It also runs isolated cases from `fixtures/ast-parity-boundaries.json`, including multiple files in one process and cross-file parser limits. Each case pins the canonical diagnostic count and compares messages, severity, filenames, spans, and multiplicity; unexpected parser or plugin diagnostics fail the check. Pass `--corpus` with a directory of repositories to compare every repository independently. A native rule should not be added to `nativeRules` or `NATIVE_REACT_DOCTOR_RULE_IDS` until both checks pass. + +The workflow builds artifacts for Linux x64/arm64, macOS x64/arm64, and Windows x64. It does not publish them. Shipping or making the native patch the default should happen only after corpus parity shows no diagnostic drift and benchmarks show at least a 15% p50 lint improvement. + +## Experimental package + +`react-doctor-rust` is a separate launcher for the source-patched binding. It does not change the `react-doctor` package, fails before startup when the matching native package is missing or incompatible, and exits on native analysis failures instead of falling back to TypeScript. + +The native workflow builds, verifies, packages, and smoke-tests Linux glibc x64/arm64, macOS x64/arm64, and Windows x64 independently. Every platform runs exact fixture parity, native scan parity, and native project-analysis parity before its artifact can enter the package assembly job. The assembly job verifies the recorded binding and patch SHA-256 hashes and a fingerprint of the current native sources, upstream manifest, build generator, and native CI configuration. Stale artifacts require a rebuild, including artifacts created before source fingerprinting. The fingerprint normalizes checkout line endings across platforms. Assembly generates one optional platform package per target and emits packed tarballs plus `SHA256SUMS`. Each target then installs and scans with those exact tarballs on its native runner. + +Both Linux architectures build in official PyPA `manylinux_2_28` containers to target glibc 2.28. These images use AlmaLinux 8; see the [manylinux image documentation](https://github.com/pypa/manylinux#manylinux_2_28-almalinux-8-based). The eight tarball smoke jobs cover both architectures on this baseline with Node 20.19.0, current Ubuntu runners, both macOS architectures, and Windows x64. Linux Cargo caches are isolated from host builds so newer glibc artifacts cannot enter the baseline build through the cache. + +Default assemblies remain private and CI-only. An explicit `--release --version -experimental.` assembly builds the CLI with that version and prepares public manifests under the `experimental` npm tag. The launcher bundles the exact workspace CLI and rule plugin using npm's bundled dependencies; it requires no separate stable-package publication. External runtime dependencies remain declared on the launcher, and `runtime-manifest.json` fingerprints every bundled file. Publication is a separate, explicitly authorized action. See [release preparation](RELEASE.md). + +The packed CI smoke installs only the launcher and its current-platform tarball. It verifies that the CLI and rule plugin resolve from the bundled runtime, checks their content hashes, and exercises complete reports and required-native failures. After publishing, run `nr native:rust:smoke -- --package react-doctor-rust@` on every supported runner. That mode installs only the registry launcher, proving npm selects its current platform binding. Do not promote beyond the experimental channel until every postpublish smoke passes. Musl and other targets are unsupported until they have native build and install smoke lanes. diff --git a/native/oxlint/RELEASE.md b/native/oxlint/RELEASE.md new file mode 100644 index 0000000000..11d6985512 --- /dev/null +++ b/native/oxlint/RELEASE.md @@ -0,0 +1,37 @@ +# Preparing react-doctor-rust + +Release preparation builds and packs artifacts. It never publishes, tags, or merges. Keep the ordinary CI assemblies private; use the explicit release mode for a reviewable experimental candidate. + +## Prepare + +Start from the reviewed commit and download its successful Native Oxlint workflow's five `native-oxlint-*` artifacts into `dist/native-oxlint-all`. Verify that its five platform parity jobs, package assembly, and eight installation smokes passed. Assembly checks every binary hash and the current native source fingerprint, so artifacts from different native sources cannot be mixed. + +```sh +nr native:rust:assemble -- --artifacts dist/native-oxlint-all --release --version 0.9.13-experimental.0 +nr native:rust:pack +nr native:rust:smoke +``` + +The version is an example candidate, not permission to publish it. Release mode rebuilds the workspace CLI with the candidate version. It bundles the packed CLI and rule plugin into the launcher using [npm bundled dependencies](https://docs.npmjs.com/files/package.json/#bundledependencies); stable packages do not need to be published. Required native execution, configuration, diagnostic reporting, and suppression remain unchanged. Existing `cli.invoked` telemetry carries the build version and existing opt-outs; no additional telemetry is introduced by packaging. + +Inspect the six tarballs, `SHA256SUMS`, `package-manifest.json`, and `release-plan.json` in `dist/react-doctor-rust-tarballs`. The release plan records exact versions, tarball hashes, and publication arguments, with all five platform packages before the launcher. Run its commands from that tarball directory. Before requesting publication approval, dry-run each exact tarball there: + +```sh +npm publish ./react-doctor-rust-0.9.13-experimental.0.tgz --dry-run --ignore-scripts --access public --tag experimental --registry https://registry.npmjs.org/ +``` + +Repeat with each platform tarball listed in the release plan. + +Check the npm registry for existing versions and confirm the publishing account can publish all six unscoped package names. A missing registry entry does not prove permission to claim a name. Published versions cannot be replaced; prepare a new experimental version if any version already exists. + +## Publication boundary + +Obtain fresh, explicit approval for the exact six package names, versions, and experimental tag in `release-plan.json` before executing its publication arguments. Do not run the repository's general `release` command: that publishes the stable Changesets packages. No release workflow, Git tag, or merge is needed to prepare these artifacts. + +After approved publication, run the registry-only smoke on every supported platform: + +```sh +nr native:rust:smoke -- --package react-doctor-rust@0.9.13-experimental.0 +``` + +Keep the channel experimental if any install or required-native analysis fails. Promotion requires separate approval and clean platform smokes, complete corpus evidence, and a controlled benchmark showing the documented improvement threshold. The current corpus completeness and performance limitations do not support promotion to the default engine. Generated hooks still invoke `react-doctor`; users must choose `react-doctor-rust` explicitly. diff --git a/native/oxlint/fixtures/ast-parity-boundaries.json b/native/oxlint/fixtures/ast-parity-boundaries.json new file mode 100644 index 0000000000..fcb0b5f9ad --- /dev/null +++ b/native/oxlint/fixtures/ast-parity-boundaries.json @@ -0,0 +1,6682 @@ +[ + { + "name": "mapped-timer-arrow-dropped-handle", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => { timers.pop(); timers.forEach(timer => clearTimeout(timer)); }; }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-arrow-conditional-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(timer => { if (enabled) clearTimeout(timer); }); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-arrow-async-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(async timer => { await refresh(); clearTimeout(timer); }); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-arrow-wrong-parameter", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach((timer, index) => clearTimeout(index)); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-arrow-wrong-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(timer => clearInterval(timer)); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-arrow-function-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(function(timer) { clearTimeout(timer); }); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-direct-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-from-direct-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = Array.from(delays, delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-block-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { const timer = setTimeout(refresh, delay); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-from-block-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = Array.from(delays, delay => { const timer = setTimeout(refresh, delay); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-block-direct-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { return setTimeout(refresh, delay); }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-wrong-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(clearInterval); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-filtered-handles", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)).filter(Boolean); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-lost-handle", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { const timer = setTimeout(refresh, delay); return delay; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-early-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { const timer = setTimeout(refresh, delay); if (delay) return; return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-pop-before-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => { timers.pop(); timers.forEach(clearTimeout); }; }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-clear-after-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => { timers.forEach(clearTimeout); timers.length = 0; }; }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-async-callback", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(async delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-let-collection", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { let timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-let-handle", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { let timer = setTimeout(refresh, delay); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-shadowed-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const clearTimeout = refresh; const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-conditional-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => { if (enabled) timers.forEach(clearTimeout); }; }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-computed-map", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays[\"map\"](delay => setTimeout(refresh, delay)); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-interval", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setInterval(refresh, delay)); return () => timers.forEach(clearInterval); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-parenthesized-allocation", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => (setTimeout(refresh, delay))); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-block-throw-after-allocation", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { const timer = setTimeout(refresh, delay); refresh(); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-computed-map-unreleased", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { delays[\"map\"](delay => setTimeout(refresh, delay)); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-computed-for-each-unreleased", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { delays[\"forEach\"](delay => setInterval(refresh, delay)); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-from-unreleased", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { Array.from(delays, delay => setTimeout(refresh, delay)); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-unreleased", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { delays.map(delay => setTimeout(refresh, delay)); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-block-function-expression", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(function(delay) { const timer = setTimeout(refresh, delay); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-alias-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); const copy = timers; return () => copy.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-copy-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); const copy = [...timers]; return () => copy.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-cleanup-arrow-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => setTimeout(refresh, delay)); return () => timers.forEach(timer => clearTimeout(timer)); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "mapped-timer-map-async-outer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { let timers = []; void (async () => { await refresh(); timers = delays.map(delay => setTimeout(refresh, delay)); })(); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-block-nested-declaration", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { { const timer = setTimeout(refresh, delay); return timer; } }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-block-return-after-throw", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { const timers = delays.map(delay => { const timer = setTimeout(refresh, delay); if (delay) throw new Error(); return timer; }); return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "mapped-timer-map-reassigned-collection", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export function Preview({ delays, refresh, enabled }) { useEffect(() => { let timers = delays.map(delay => setTimeout(refresh, delay)); timers = []; return () => timers.forEach(clearTimeout); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "rn-scrollview-direct-parameter-default-web-framework-suppressed", + "ruleId": "rn-scrollview-flex-in-content-container", + "capabilities": ["react", "react-native"], + "source": "import { ScrollView, StyleSheet } from 'react-native'; export function Screen(styles = StyleSheet.create({ container: { flex: 1 } })) { return ; }", + "expectedCount": 0 + }, + { + "name": "rn-gesture-direct-parameter-default-web-framework-suppressed", + "ruleId": "rn-prefer-pressable-over-gesture-detector", + "capabilities": ["react", "react-native"], + "source": "import { Gesture, GestureDetector } from 'react-native-gesture-handler'; export function Screen(tap = Gesture.Tap()) { return ; }", + "expectedCount": 0 + }, + { + "name": "upstream-collection-release-wrong-timer-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearInterval); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-collection-release-shadowed-timer-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const clearTimeout = () => {}; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-collection-release-dropped-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.pop(); timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-collection-release-escaped-timers", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; leak(timers); timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-collection-release-helper-drops-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); const drop = () => timers.pop(); return () => { drop(); run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-collection-release-cleared-after-release", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); timers.length = 0; }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-collection-release-unrelated-inert-mutation", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const unused = () => timers.pop(); const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "rn-scrollview-direct-parameter-default", + "ruleId": "rn-scrollview-flex-in-content-container", + "framework": "react-native", + "capabilities": ["react", "react-native"], + "source": "import { ScrollView, StyleSheet } from 'react-native'; export function Screen(styles = StyleSheet.create({ container: { flex: 1 } })) { return ; }", + "expectedCount": 1 + }, + { + "name": "rn-scrollview-destructured-container-default-unresolved", + "ruleId": "rn-scrollview-flex-in-content-container", + "framework": "react-native", + "capabilities": ["react", "react-native"], + "source": "import { ScrollView, StyleSheet } from 'react-native'; export function Screen({ styles } = { styles: StyleSheet.create({ container: { flex: 1 } }) }) { return ; }", + "expectedCount": 0 + }, + { + "name": "rn-gesture-direct-parameter-default", + "ruleId": "rn-prefer-pressable-over-gesture-detector", + "framework": "react-native", + "capabilities": ["react", "react-native"], + "source": "import { Gesture, GestureDetector } from 'react-native-gesture-handler'; export function Screen(tap = Gesture.Tap()) { return ; }", + "expectedCount": 1 + }, + { + "name": "rn-gesture-destructured-container-default-unresolved", + "ruleId": "rn-prefer-pressable-over-gesture-detector", + "framework": "react-native", + "capabilities": ["react", "react-native"], + "source": "import { Gesture, GestureDetector } from 'react-native-gesture-handler'; export function Screen({ tap } = { tap: Gesture.Tap() }) { return ; }", + "expectedCount": 0 + }, + { + "name": "upstream-review-object-negated-initializer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: !false }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-object-negated-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = !true; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-object-computed-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run['live'] = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-object-computed-guard", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run['live']) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-collection-loop", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { for (const item of items) timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-===-true-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (true === run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-===-true-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live === true) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-===-false-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (false === run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-===-false-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live === false) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-==-true-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (true == run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-==-true-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live == true) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-==-false-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (false == run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-==-false-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live == false) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-!==-true-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (true !== run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-!==-true-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live !== true) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-!==-false-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (false !== run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-!==-false-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live !== false) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-!=-true-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (true != run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-!=-true-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live != true) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-comparison-!=-false-left", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (false != run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-comparison-!=-false-right", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live != false) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-bare-negated", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-consequent-conjunction", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (run.live && enabled) { schedule(); } })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-alternate-guard", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) {} else { schedule(); } })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-async-unary-comparison", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!(run.live === true)) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-async-or-early-exit", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live || !enabled) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-storage-object-other-read", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const storage = { timer: null }; console.log(storage.other); const schedule = () => { storage.timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (storage.timer !== null) clearTimeout(storage.timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-storage-object-other-write", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const storage = { timer: null }; storage.other = 1; const schedule = () => { storage.timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (storage.timer !== null) clearTimeout(storage.timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-storage-collection-const-empty", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-storage-collection-let-empty", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timers = []; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-storage-collection-const-nonempty", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = [existing]; const schedule = () => { timers.push(setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-storage-collection-computed-push", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const timers = []; const schedule = () => { timers['push'](setTimeout(() => {}, 100)); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; timers.forEach(clearTimeout); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-async-console", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; console.log('ready'); schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-async-performance", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; performance.now(); schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-async-math", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; Math.round(1.2); schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-async-early-noop-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { if (!enabled) return () => {}; const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-async-object-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; const storage = { timer: null }; const schedule = () => { storage.timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (storage.timer !== null) clearTimeout(storage.timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-abort-signal-default", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const { signal = fallback } = controller; const handler = () => {}; document.addEventListener('change', handler); signal.addEventListener('abort', () => { document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-review-abort-signal-renamed", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const { signal: renamed } = controller; const handler = () => {}; document.addEventListener('change', handler); renamed.addEventListener('abort', () => { document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-review-abort-signal-nested", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const { signal: { nested } } = controller; const handler = () => {}; document.addEventListener('change', handler); nested.addEventListener('abort', () => { document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "compiler-memo-missing-argument", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from 'react'; export const View = memo();", + "expectedCount": 1 + }, + { + "name": "compiler-memo-spread-argument", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from 'react'; export const View = memo(...components);", + "expectedCount": 1 + }, + { + "name": "compiler-module-opt-out", + "ruleId": "react-compiler-no-manual-memoization", + "source": "\"use no memo\"; import {useMemo,memo} from \"react\"; export const View=memo(function View(){const value=useMemo(()=>1,[]);return
{value}
});", + "expectedCount": 0 + }, + { + "name": "compiler-module-forget-alias", + "ruleId": "react-compiler-no-manual-memoization", + "source": "\"use no forget\"; import {memo} from \"react\"; export const View=memo(()=>
);", + "expectedCount": 0 + }, + { + "name": "compiler-nearest-function-opt-out", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {useMemo} from \"react\"; export function View(){\"use no memo\"; const value=useMemo(()=>1,[]);return
{value}
}", + "expectedCount": 0 + }, + { + "name": "compiler-nested-function-still-fires", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {useMemo} from \"react\"; export function View(){\"use no memo\"; const Child=()=>{const value=useMemo(()=>1,[]);return
{value}
};return }", + "expectedCount": 1 + }, + { + "name": "compiler-late-string-still-fires", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {useMemo} from \"react\"; export function View(){prepare();\"use no memo\";const value=useMemo(()=>1,[]);return
{value}
}", + "expectedCount": 1 + }, + { + "name": "compiler-memo-function-declaration", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; function View(){\"use no memo\";return
};export default memo(View);", + "expectedCount": 0 + }, + { + "name": "compiler-memo-local-function-cast", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; const View=()=>{\"use no memo\";return
};export default memo(View as any);", + "expectedCount": 0 + }, + { + "name": "compiler-memo-inline-function-cast", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; export default memo((()=>{\"use no memo\";return
}) as any);", + "expectedCount": 0 + }, + { + "name": "compiler-memo-one-hop-alias-still-fires", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; const View=()=>{\"use no memo\";return
};const Alias=View;export default memo(Alias);", + "expectedCount": 1 + }, + { + "name": "compiler-memo-param-default", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; export function Wrap(View=()=>{\"use no memo\";return
}){return memo(View)}", + "expectedCount": 0 + }, + { + "name": "compiler-memo-destructured-param-default", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; export function Wrap({View=()=>{\"use no memo\";return
}}){return memo(View)}", + "expectedCount": 0 + }, + { + "name": "compiler-memo-destructured-object-not-resolved", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; const {View}={View:()=>{\"use no memo\";return
}};export default memo(View);", + "expectedCount": 1 + }, + { + "name": "compiler-memo-initializer-reassigned", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; let View=()=>{\"use no memo\";return
};View=Other;export default memo(View);", + "expectedCount": 0 + }, + { + "name": "compiler-memo-shadowed-param-still-fires", + "ruleId": "react-compiler-no-manual-memoization", + "source": "import {memo} from \"react\"; const View=()=>{\"use no memo\";return
};export function Wrap(View){return memo(View)}", + "expectedCount": 1 + }, + { + "name": "get-helper-safe-first-parameter", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(target){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(headers);return new Response()}", + "expectedCount": 0 + }, + { + "name": "get-helper-safe-second-parameter", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(status,target){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(200,headers);return new Response()}", + "expectedCount": 0 + }, + { + "name": "get-helper-different-helper-not-safe", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "const cache=new Map();function apply(target){target.set(\"x\",\"y\")}function mutate(target){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(headers);mutate(cache);return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-safe-and-external-calls", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "const cache=new Map();function apply(target){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(headers);apply(cache);return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-external-then-safe-calls", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "const cache=new Map();function apply(target){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(cache);apply(headers);return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-destructured-parameter-not-safe", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply({target}){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply({target:headers});return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-default-parameter-not-safe", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(target=new Headers()){target.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(headers);return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-inline-construction-not-transferred", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(target){target.set(\"x\",\"y\")}export function GET(){apply(new Headers());return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-helper-local-not-collected", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(){const target=new Headers();target.set(\"x\",\"y\")}export function GET(){apply();return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-handler-safe-name-inherited", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "const cache=new Map();function apply(headers){headers.set(\"x\",\"y\")}export function GET(){const headers=new Headers();apply(cache);return new Response()}", + "expectedCount": 0 + }, + { + "name": "get-helper-handler-cookie-name-inherited", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(jar){jar.delete(\"x\")}export async function GET(){const jar=await cookies();apply(jar);return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-helper-local-cookie-generic-message", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function apply(){const jar=cookies();jar.delete(\"x\")}export function GET(){apply();return new Response()}", + "expectedCount": 1 + }, + { + "name": "get-helper-one-hop-only", + "ruleId": "nextjs-no-side-effect-in-get-handler", + "filename": "src/app/api/cache/route.ts", + "source": "function deeper(){cache.set(\"x\",\"y\")}function apply(){deeper()}export function GET(){apply();return new Response()}", + "expectedCount": 0 + }, + { + "name": "upstream-defer-run-live-guard", + "ruleId": "async-defer-await", + "source": "declare const load: () => Promise; const run = {live: true}; export const effect = async () => { const value = await load(); if (!run.live) return; console.log(value); };", + "expectedCount": 0 + }, + { + "name": "upstream-defer-run-is-live-guard", + "ruleId": "async-defer-await", + "source": "declare const load: () => Promise; const run = {isLive: true}; export const effect = async () => { const value = await load(); if (!run.isLive) return; console.log(value); };", + "expectedCount": 0 + }, + { + "name": "upstream-defer-bare-live-guard", + "ruleId": "async-defer-await", + "source": "declare const load: () => Promise; let live = true; export const effect = async () => { const value = await load(); if (!live) return; console.log(value); };", + "expectedCount": 0 + }, + { + "name": "upstream-defer-unrelated-delivery-guard", + "ruleId": "async-defer-await", + "source": "declare const load: () => Promise; declare const deliverNow: boolean; export const effect = async () => { const value = await load(); if (!deliverNow) return; console.log(value); };", + "expectedCount": 1 + }, + { + "name": "upstream-defer-unrelated-member-delivery-guard", + "ruleId": "async-defer-await", + "source": "declare const load: () => Promise; const run = {deliverNow: true}; export const effect = async () => { const value = await load(); if (!run.deliverNow) return; console.log(value); };", + "expectedCount": 1 + }, + { + "name": "upstream-mutation-magic-link-function", + "ruleId": "query-mutation-missing-invalidation", + "source": "import {useMutation} from '@tanstack/react-query'; export const useAuthenticationEmail = () => useMutation({mutationFn: email => api.sendMagicLink(email)});", + "expectedCount": 0 + }, + { + "name": "upstream-mutation-magic-link-hook", + "ruleId": "query-mutation-missing-invalidation", + "source": "import {useMutation} from '@tanstack/react-query'; export const useSendMagicLink = () => useMutation({mutationFn: email => api.deliver(email)});", + "expectedCount": 0 + }, + { + "name": "upstream-mutation-generic-email-still-fires", + "ruleId": "query-mutation-missing-invalidation", + "source": "import {useMutation} from '@tanstack/react-query'; export const useEmailInvite = () => useMutation({mutationFn: email => api.emailInvite(email)});", + "expectedCount": 1 + }, + { + "name": "upstream-mutation-generic-send-still-fires", + "ruleId": "query-mutation-missing-invalidation", + "source": "import {useMutation} from '@tanstack/react-query'; export const useSendMessage = () => useMutation({mutationFn: message => api.sendMessage(message)});", + "expectedCount": 1 + }, + { + "name": "upstream-mutation-generic-notify-still-fires", + "ruleId": "query-mutation-missing-invalidation", + "source": "import {useMutation} from '@tanstack/react-query'; export const useNotifyUser = () => useMutation({mutationFn: user => api.notifyUser(user)});", + "expectedCount": 1 + }, + { + "name": "upstream-1756-accepts-chained-timers-cleared-by-the-returned-cleanup-helper", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect, useState } from \"react\";\n\n const STEPS = [0, 400, 800];\n\n const ChainedTimer = () => {\n const [frame, setFrame] = useState(0);\n const [loop, setLoop] = useState(0);\n useEffect(() => {\n let cancelled = false;\n let timer: ReturnType | null = null;\n let index = 0;\n const startedAt = Date.now();\n const clearTimer = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n };\n const scheduleAt = (at: number, callback: () => void) => {\n timer = setTimeout(callback, Math.max(0, at - (Date.now() - startedAt)));\n };\n const advance = () => {\n timer = null;\n if (cancelled) return;\n setFrame(index);\n index += 1;\n if (index < STEPS.length) {\n scheduleAt(STEPS[index], advance);\n } else {\n scheduleAt(2000, () => {\n if (!cancelled) setLoop((count) => count + 1);\n });\n }\n };\n const onVisibilityChange = () => {\n if (cancelled) return;\n if (document.hidden) clearTimer();\n else setLoop((count) => count + 1);\n };\n document.addEventListener(\"visibilitychange\", onVisibilityChange);\n if (!document.hidden) scheduleAt(STEPS[0], advance);\n return () => {\n cancelled = true;\n clearTimer();\n document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n };\n }, [loop]);\n return {frame};\n };\n ", + "expectedCount": 0 + }, + { + "name": "upstream-1756-reports-timer-handle-overwrites-before-the-previous-timer-fires", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect } from \"react\";\n\n const UnsafeTimers = () => {\n useEffect(() => {\n let timer: ReturnType | null = null;\n const schedule = () => {\n timer = setTimeout(() => {}, 1000);\n };\n schedule();\n schedule();\n return () => {\n if (timer !== null) clearTimeout(timer);\n };\n }, []);\n return null;\n };\n ", + "expectedCount": 1 + }, + { + "name": "upstream-1756-accepts-a-guarded-timer-scheduled-through-a-helper-after-an-await", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect, useState } from \"react\";\n\n declare function refresh(): Promise<\"refreshed\" | \"invalid\">;\n\n const AsyncBoundedTimer = () => {\n const [unproven, setUnproven] = useState(false);\n useEffect(() => {\n const run = { live: true };\n let settleTimer: ReturnType | null = null;\n const refreshAndBound = () => {\n settleTimer = setTimeout(() => {\n settleTimer = null;\n if (run.live) setUnproven(true);\n }, 4000);\n };\n void (async () => {\n const outcome = await refresh();\n if (!run.live) return;\n if (outcome === \"refreshed\") {\n refreshAndBound();\n return;\n }\n setUnproven(true);\n })();\n return () => {\n run.live = false;\n if (settleTimer !== null) clearTimeout(settleTimer);\n };\n }, []);\n return {String(unproven)};\n };\n ", + "expectedCount": 0 + }, + { + "name": "upstream-1756-reports-a-helper-timer-scheduled-after-an-await-without-a-lifecycle-guard", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect } from \"react\";\n\n declare function refresh(): Promise;\n\n const UnsafeAsyncTimer = () => {\n useEffect(() => {\n let timer: ReturnType | null = null;\n const schedule = () => {\n timer = setTimeout(() => {}, 1000);\n };\n void (async () => {\n await refresh();\n schedule();\n })();\n return () => {\n if (timer !== null) clearTimeout(timer);\n };\n }, []);\n return null;\n };\n ", + "expectedCount": 1 + }, + { + "name": "upstream-1756-reports-an-interruption-between-the-lifecycle-guard-and-timer-helper", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect } from \"react\";\n\n declare function refresh(): Promise;\n\n const UnsafeInterruptedTimer = () => {\n useEffect(() => {\n const run = { live: true };\n let timer: ReturnType | null = null;\n const schedule = () => {\n timer = setTimeout(() => {}, 1000);\n };\n void (async () => {\n await refresh();\n if (!run.live) return;\n await refresh();\n schedule();\n })();\n return () => {\n run.live = false;\n if (timer !== null) clearTimeout(timer);\n };\n }, []);\n return null;\n };\n ", + "expectedCount": 1 + }, + { + "name": "upstream-1756-reports-guarded-async-timer-work-started-repeatedly-by-the-effect", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect } from \"react\";\n\n declare function refresh(): Promise;\n\n const UnsafeRepeatedAsyncTimer = () => {\n useEffect(() => {\n const run = { live: true };\n let timer: ReturnType | null = null;\n const schedule = () => {\n timer = setTimeout(() => {}, 1000);\n };\n for (let index = 0; index < 2; index += 1) {\n void (async () => {\n await refresh();\n if (!run.live) return;\n schedule();\n })();\n }\n return () => {\n run.live = false;\n if (timer !== null) clearTimeout(timer);\n };\n }, []);\n return null;\n };\n ", + "expectedCount": 1 + }, + { + "name": "upstream-1756-accepts-a-listener-released-by-an-abort-handler-when-cleanup-aborts-the-controller", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect, useState } from \"react\";\n\n const AbortTeardown = () => {\n const [visible, setVisible] = useState(true);\n useEffect(() => {\n const controller = new AbortController();\n const { signal } = controller;\n const onChange = () => setVisible(!document.hidden);\n document.addEventListener(\"visibilitychange\", onChange);\n signal.addEventListener(\n \"abort\",\n () => {\n document.removeEventListener(\"visibilitychange\", onChange);\n },\n { once: true },\n );\n return () => {\n controller.abort();\n };\n }, []);\n return {visible ? \"visible\" : \"hidden\"};\n };\n ", + "expectedCount": 0 + }, + { + "name": "upstream-1756-reports-an-abort-handler-that-removes-a-different-listener", + "ruleId": "effect-needs-cleanup", + "source": "\n import { useEffect } from \"react\";\n\n const UnsafeAbortTeardown = () => {\n useEffect(() => {\n const controller = new AbortController();\n const onChange = () => {};\n const otherHandler = () => {};\n document.addEventListener(\"visibilitychange\", onChange);\n controller.signal.addEventListener(\"abort\", () => {\n document.removeEventListener(\"visibilitychange\", otherHandler);\n });\n return () => controller.abort();\n }, []);\n return null;\n };\n ", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-abort-signal-member-alias", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const handler = () => {}; document.addEventListener('change', handler); signal.addEventListener('abort', () => { document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-abort-signal-destructured", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const { signal } = controller; const handler = () => {}; document.addEventListener('change', handler); signal.addEventListener('abort', () => { document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-abort-handler-conditional-removal", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const handler = () => {}; document.addEventListener('change', handler); signal.addEventListener('abort', () => { if (ready) document.removeEventListener('change', handler); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-abort-handler-wrong-capture", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const handler = () => {}; document.addEventListener('change', handler, true); signal.addEventListener('abort', () => { document.removeEventListener('change', handler, false); }); return () => controller.abort(); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-abort-handler-external-controller", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const handler = () => {}; document.addEventListener('change', handler); controller.signal.addEventListener('abort', () => document.removeEventListener('change', handler)); return () => controller.abort(); }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-abort-handler-no-returned-abort", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const handler = () => {}; document.addEventListener('change', handler); controller.signal.addEventListener('abort', () => document.removeEventListener('change', handler)); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-single-direct-timer-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; schedule(); return () => clearTimeout(timer); }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-repeated-direct-timer-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; schedule(); schedule(); return () => clearTimeout(timer); }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-boolean-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { let live = true; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!live) return; schedule(); })(); return () => { live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-guarded-async-object-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-guarded-async-object-extra-property", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true, extra: 1 }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-object-escape", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; leak(run); let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-object-reactivation", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; run.live = true; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-await-after-guard", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; await refresh(); schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-call-after-guard", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; refresh(); schedule(); })(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-async-wrong-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; void (async () => { await refresh(); if (!run.live) return; schedule(); })(); return () => { run.live = true; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "upstream-cleanup-guarded-named-async-once", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; const start = async () => { await refresh(); if (!run.live) return; schedule(); }; start(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 0 + }, + { + "name": "upstream-cleanup-guarded-named-async-twice", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ controller, items }) => { useEffect(() => { const run = { live: true }; let timer = null; const schedule = () => { timer = setTimeout(() => {}, 100); }; const start = async () => { await refresh(); if (!run.live) return; schedule(); }; start(); start(); return () => { run.live = false; if (timer !== null) clearTimeout(timer); }; }, []); return null; };", + "expectedCount": 1 + }, + { + "name": "self-recursive-helper-terminates", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; const recurse = () => recurse(); export const Preview = () => recurse() ?
: ;", + "expectedCount": 0 + }, + { + "name": "mutually-recursive-helpers-terminate", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; const first = () => second(); const second = () => first(); export const Preview = () => first() ?
: ;", + "expectedCount": 0 + }, + { + "name": "parameterized-recursive-helper-terminates", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; const recurse = (value) => recurse(value); export const Preview = () => recurse(true) ?
: ;", + "expectedCount": 0 + }, + { + "name": "recursive-edge-retains-independent-browser-predicate", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; const recurse = () => recurse() || typeof window !== \"undefined\"; export const Preview = () => recurse() ?
: ;", + "expectedCount": 1 + }, + { + "name": "type-only-react-import-is-client-evidence", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import type { ReactNode } from \"react\"; export const Preview = (): ReactNode => typeof window === \"undefined\" ?
:
;", + "expectedCount": 1 + }, + { + "name": "side-effect-react-import-is-client-evidence", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import \"react\"; export const Preview = () => typeof document === \"undefined\" ?
:
;", + "expectedCount": 1 + }, + { + "name": "missing-react-evidence-remains-suppressed", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "export const Preview = () => typeof document === \"undefined\" ?
:
;", + "expectedCount": 0 + }, + { + "name": "nested-strict-boolean-equality-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; export const Preview = () => (typeof window !== \"undefined\") === true ?
:
;", + "expectedCount": 1 + }, + { + "name": "nested-strict-equality-invariant-remains-suppressed", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; export const Preview = () => (typeof window !== \"undefined\") === 42 ?
:
;", + "expectedCount": 0 + }, + { + "name": "nested-loose-equality-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; export const Preview = () => (typeof window !== \"undefined\") == 1 ?
:
;", + "expectedCount": 1 + }, + { + "name": "reflexive-runtime-alias-remains-suppressed", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; export const Preview = () => { const runtime = typeof window === \"undefined\" ? \"server\" : \"client\"; return runtime === runtime ?
:
; };", + "expectedCount": 0 + }, + { + "name": "unknown-runtime-value-equality-preserves-match", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "\"use client\"; const getMode = () => typeof document === \"undefined\" ? \"system\" : localStorage.getItem(\"mode\"); export const Preview = () => { const mode = getMode(); return mode === \"dark\" ?
:
; };", + "expectedCount": 1 + }, + { + "name": "aliased-import-browser-predicate-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { isBrowser } from \"@/runtime\"; export const Preview = () => isBrowser() ?
:
;", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export function isBrowser() { return typeof document !== \"undefined\"; }" + } + }, + { + "name": "aliased-import-unknown-equality-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { getMode } from \"@/runtime\"; export const Preview = () => { const mode = getMode(); return mode === \"dark\" ?
:
; };", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export function getMode() { return validateMode(typeof document === \"undefined\" ? \"system\" : localStorage.getItem(\"mode\")); } function validateMode(mode) { return mode === \"dark\" || mode === \"light\" ? mode : \"system\"; }" + } + }, + { + "name": "aliased-import-unrelated-browser-helper-does-not-fire", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { readValue } from \"@/runtime\"; export const Preview = () => readValue() ?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export function readValue() { return 1; } export function isBrowser() { return typeof document !== \"undefined\"; }" + } + }, + { + "name": "memoized-relative-import-unrelated-browser-helper-does-not-fire", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import { useMemo } from \"react\"; import { readValue } from \"./runtime\"; export const Preview = ({ enabled }) => { const value = useMemo(() => { if (!enabled) return false; return readValue({ enabled }); }, [enabled]); return value &&
; };", + "expectedCount": 0, + "files": { + "src/runtime.ts": "export const readValue = ({ enabled }) => enabled; export function isBrowser() { return typeof document !== \"undefined\"; }" + } + }, + { + "name": "imported-destructured-parameter-remains-unsupported", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { readValue } from \"./runtime\"; export const Preview = () => readValue({}) ?
:
;", + "expectedCount": 0, + "files": { + "src/runtime.ts": "export function readValue({ enabled }) { return typeof document !== \"undefined\"; }" + } + }, + { + "name": "relative-import-browser-helper-still-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { isBrowser } from \"./runtime\"; export const Preview = () => isBrowser() ?
:
;", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export function isBrowser() { return typeof document !== \"undefined\"; }" + } + }, + { + "name": "function-body-following-return-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; export function Preview() { if (typeof window === \"undefined\") return
; return
; }", + "expectedCount": 1 + }, + { + "name": "function-body-wrapper-versus-children-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; export const Preview = (props) => { if (typeof window !== \"undefined\") return
{props.children}
; return props.children; };", + "expectedCount": 1 + }, + { + "name": "function-body-fragment-versus-provider-fires", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; export function Preview({ children }) { if (typeof window !== \"undefined\") return <>{children}; return {children}; }", + "expectedCount": 1 + }, + { + "name": "function-body-equivalent-following-return-does-not-fire", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; export function Preview() { if (typeof window === \"undefined\") return
; return
; }", + "expectedCount": 0 + }, + { + "name": "function-body-initial-mount-return-remains-suppressed", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import { useState } from \"react\"; export function Preview() { const [mounted] = useState(false); if (!mounted) return null; if (typeof window === \"undefined\") return
; return
; }", + "expectedCount": 0 + }, + { + "name": "cross-context-parameter-cycle-terminates", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { checkValue } from \"./runtime\"; export const Preview = ({ value }) => checkValue(value) ?
:
;", + "expectedCount": 0, + "files": { + "src/runtime.ts": "export const checkValue = (value) => isReady(value); const isReady = (value) => value === \"ready\";" + } + }, + { + "name": "cross-context-parameter-cycle-retains-independent-browser-predicate", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/page.tsx", + "source": "import React from \"react\"; import { checkValue } from \"./runtime\"; export const Preview = ({ value }) => checkValue(value) ?
:
;", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const checkValue = (value) => isReady(value) || typeof document !== \"undefined\"; const isReady = (value) => value === \"ready\";" + } + }, + { + "name": "missing-dependency-suppresses-broader-dependency-redundancy", + "ruleId": "exhaustive-deps", + "source": "import { useCallback, useState } from 'react'; export function Component(props) { const [count] = useState(0); return useCallback(() => props.value + count, [props, props.value]); }", + "expectedCount": 1 + }, + { + "name": "broader-dependency-redundancy-with-complete-dependencies", + "ruleId": "exhaustive-deps", + "source": "import { useCallback, useState } from 'react'; export function Component(props) { const [count] = useState(0); return useCallback(() => props.value + count, [props, props.value, count]); }", + "expectedCount": 1 + }, + { + "name": "module-array-reports-only-outer-scope-dependency", + "ruleId": "exhaustive-deps", + "source": "import { useMemo } from 'react'; const elements = [{ value: 1 }]; export function Component() { return useMemo(() => elements.map(element => element.value), [elements]); }", + "expectedCount": 1 + }, + { + "name": "render-local-array-still-reports-unstable-dependency", + "ruleId": "exhaustive-deps", + "source": "import { useMemo } from 'react'; export function Component() { const elements = [{ value: 1 }]; return useMemo(() => elements.map(element => element.value), [elements]); }", + "expectedCount": 1 + }, + { + "name": "default-callback-capturing-enclosing-factory-parameter-is-reactive", + "ruleId": "exhaustive-deps", + "source": "import { useCallback } from 'react'; export function makeHook(outer) { return function useThing({ serialize = value => String(value) + outer }) { return useCallback(value => serialize(value), []); }; }", + "expectedCount": 1 + }, + { + "name": "default-callback-capturing-only-module-value-is-stable", + "ruleId": "exhaustive-deps", + "source": "import { useCallback } from 'react'; const suffix = 'suffix'; export function useThing({ serialize = value => String(value) + suffix }) { return useCallback(value => serialize(value), []); }", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-commonjs-namespace-is-outside-canonical-contract", + "ruleId": "no-react-dom-deprecated-apis", + "source": "const ReactDOM = require('react-dom'); ReactDOM.render(
, document.body);", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-import-equals-is-outside-canonical-contract", + "ruleId": "no-react-dom-deprecated-apis", + "filename": "src/import-equals.tsx", + "source": "import ReactDOM = require('react-dom'); ReactDOM.render(
, document.body);", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-default-import", + "ruleId": "no-react-dom-deprecated-apis", + "source": "import ReactDOM from 'react-dom'; ReactDOM.render(
, document.body);", + "expectedCount": 1 + }, + { + "name": "deprecated-dom-namespace-const-alias", + "ruleId": "no-react-dom-deprecated-apis", + "source": "import * as ReactDOM from 'react-dom'; const DomAlias = ReactDOM; DomAlias.render(
, document.body);", + "expectedCount": 1 + }, + { + "name": "deprecated-dom-named-default-is-not-recorded-as-namespace", + "ruleId": "no-react-dom-deprecated-apis", + "source": "import { default as ReactDOM } from 'react-dom'; ReactDOM.render(
, document.body);", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-type-default-import-is-ignored", + "ruleId": "no-react-dom-deprecated-apis", + "filename": "src/type-default.tsx", + "source": "import type ReactDOM from 'react-dom'; ReactDOM.render(
, document.body);", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-inline-type-named-import-is-ignored", + "ruleId": "no-react-dom-deprecated-apis", + "filename": "src/type-named.tsx", + "source": "import { type render } from 'react-dom'; export interface RenderOptions { callback: typeof render }", + "expectedCount": 0 + }, + { + "name": "deprecated-dom-named-api-import", + "ruleId": "no-react-dom-deprecated-apis", + "source": "import { render } from 'react-dom'; render(
, document.body);", + "expectedCount": 1 + }, + { + "name": "deprecated-test-utils-type-import-retains-canonical-extra-source-handler", + "ruleId": "no-react-dom-deprecated-apis", + "filename": "src/test-utils-type.tsx", + "source": "import type { act } from 'react-dom/test-utils'; export interface ActOptions { callback: typeof act }", + "expectedCount": 1 + }, + { + "name": "legacy-context-commonjs-namespace-is-outside-canonical-contract", + "ruleId": "no-legacy-context-api", + "source": "const React = require('react'); export class Component extends React.Component { static contextTypes = {}; getChildContext() { return {}; } }", + "expectedCount": 0 + }, + { + "name": "legacy-context-import-equals-is-outside-canonical-contract", + "ruleId": "no-legacy-context-api", + "filename": "src/legacy-import-equals.tsx", + "source": "import React = require('react'); export class Component extends React.Component { static contextTypes = {}; }", + "expectedCount": 0 + }, + { + "name": "legacy-context-default-import", + "ruleId": "no-legacy-context-api", + "source": "import React from 'react'; export class Component extends React.Component { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-namespace-const-alias", + "ruleId": "no-legacy-context-api", + "source": "import * as React from 'react'; const ReactAlias = React; export class Component extends ReactAlias.Component { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-named-default-retains-canonical-namespace-support", + "ruleId": "no-legacy-context-api", + "source": "import { default as React } from 'react'; export class Component extends React.Component { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-type-default-retains-canonical-import-recognition", + "ruleId": "no-legacy-context-api", + "filename": "src/legacy-type-default.tsx", + "source": "import type React from 'react'; export class Component extends React.Component { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-type-named-retains-canonical-import-recognition", + "ruleId": "no-legacy-context-api", + "filename": "src/legacy-type-named.tsx", + "source": "import type { Component as ReactComponent } from 'react'; export class Component extends ReactComponent { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-preact-compat-runtime-source", + "ruleId": "no-legacy-context-api", + "source": "import React from 'preact/compat'; export class Component extends React.Component { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-react-dom-runtime-source", + "ruleId": "no-legacy-context-api", + "source": "import { Component as ReactComponent } from 'react-dom'; export class Component extends ReactComponent { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-named-component-const-alias", + "ruleId": "no-legacy-context-api", + "source": "import { Component as ReactComponent } from 'react'; const Base = ReactComponent; export class Component extends Base { static contextTypes = {}; }", + "expectedCount": 1 + }, + { + "name": "legacy-context-unrelated-class-is-ignored", + "ruleId": "no-legacy-context-api", + "source": "export class Component { static contextTypes = {}; getChildContext() { return {}; } }", + "expectedCount": 0 + }, + { + "name": "legacy-context-commonjs-class-assignment-is-ignored", + "ruleId": "no-legacy-context-api", + "source": "const React = require('react'); class Component extends React.Component {} Component.contextTypes = {};", + "expectedCount": 0 + }, + { + "name": "legacy-context-commonjs-class-alias-assignment-is-ignored", + "ruleId": "no-legacy-context-api", + "source": "const React = require('react'); class Component extends React.Component {} const Alias = Component; Alias.contextTypes = {};", + "expectedCount": 0 + }, + { + "name": "legacy-context-esm-class-alias-assignment-is-retained", + "ruleId": "no-legacy-context-api", + "source": "import React from 'react'; class Component extends React.Component {} const Alias = Component; Alias.contextTypes = {};", + "expectedCount": 1 + }, + { + "name": "array-predicate-child-data-direct", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ rows, onChange }) { const selected = new Set(['a']); useEffect(() => { onChange(rows.filter(row => selected.has(row.id))); }, [rows, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "Seanime" + }, + { + "name": "array-predicate-child-data-object-logical", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ rows, onChange }) { const selected = new Set(['a']); useEffect(() => { onChange({ rows: rows.filter(row => selected.has(row.id)) ?? [] }); }, [rows, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "Seanime" + }, + { + "name": "array-predicate-parent-only-object-logical", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ rows, selected, onChange }) { useEffect(() => { onChange({ rows: rows.filter(row => selected.includes(row.id)) ?? [] }); }, [rows, selected, onChange]); return null; }", + "expectedCount": 0, + "sourceReference": "Seanime" + }, + { + "name": "state-initializer-mutable-prop-parts-parse-int", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; export function Child({ initial, onChange }) { let parts = []; if (initial === 'custom') parts = ['1', 'day']; else parts = initial.split('-'); const [value] = useState(parseInt(parts[0])); useEffect(() => { onChange(`${value}`); }, [value, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CustomUpcomingLength" + }, + { + "name": "state-initializer-immutable-prop-parts-parse-int", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; export function Child({ initial, onChange }) { const parts = initial.split('-'); const [value] = useState(parseInt(parts[0])); useEffect(() => { onChange(`${value}`); }, [value, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CustomUpcomingLength" + }, + { + "name": "state-initializer-mutable-prop-parts-without-parse-int", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; export function Child({ initial, onChange }) { let parts = []; if (initial === 'custom') parts = ['1', 'day']; else parts = initial.split('-'); const [value] = useState(parts[0]); useEffect(() => { onChange(`${value}`); }, [value, onChange]); return null; }", + "expectedCount": 0, + "sourceReference": "CustomUpcomingLength" + }, + { + "name": "state-initializer-global-parse-int-literal", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; export function Child({ onChange }) { const [value] = useState(parseInt('1')); useEffect(() => { onChange(value); }, [value, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CustomUpcomingLength" + }, + { + "name": "live-state-void-transparent-async-data-prop-reader", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useCallback, useEffect, useState } from 'react'; export function Child({ accountId }) { const [count] = useState(0); const read = useCallback(async value => { console.log(accountId, value); }, [accountId]); useEffect(() => { void read(count); }, [read, count]); return null; }", + "expectedCount": 1, + "sourceReference": "ImportTransactionsModal" + }, + { + "name": "live-state-transparent-async-data-prop-reader", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useCallback, useEffect, useState } from 'react'; export function Child({ accountId }) { const [count] = useState(0); const read = useCallback(async value => { console.log(accountId, value); }, [accountId]); useEffect(() => { read(count); }, [read, count]); return null; }", + "expectedCount": 1, + "sourceReference": "ImportTransactionsModal" + }, + { + "name": "live-state-void-direct-parent-callback", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect, useState } from 'react'; export function Child({ onChange }) { const [count] = useState(0); useEffect(() => { void onChange(count); }, [onChange, count]); return null; }", + "expectedCount": 0, + "sourceReference": "ImportTransactionsModal" + }, + { + "name": "effect-event-nested-parent-notification-and-fetched-local-data", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useEffectEvent, useState } from 'react'; import { fetchRows } from 'data-service'; export function Child({ setLoading }) { const [rows, setRows] = useState([]); async function loadRows() { const nextRows = await fetchRows(); setRows(nextRows); } const init = useEffectEvent(() => { async function loadData() { await loadRows(); setLoading(false); } void loadData(); }); useEffect(() => { return init(); }, []); return null; }", + "expectedCount": 1, + "sourceReference": "ManageRules" + }, + { + "name": "effect-event-direct-parent-notification-and-fetched-data", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useEffectEvent } from 'react'; import { readRows } from 'data-service'; export function Child({ onChange }) { const init = useEffectEvent(() => { const rows = readRows(); onChange(rows); }); useEffect(() => { return init(); }, []); return null; }", + "expectedCount": 1, + "sourceReference": "ManageRules" + }, + { + "name": "effect-event-nested-parent-notification-static-only", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useEffectEvent } from 'react'; export function Child({ setLoading }) { const init = useEffectEvent(() => { async function loadData() { setLoading(false); } void loadData(); }); useEffect(() => { return init(); }, []); return null; }", + "expectedCount": 0, + "sourceReference": "ManageRules" + }, + { + "name": "ref-callback-parameter-destructure-stable-assignment", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useRef } from 'react'; import { createValue } from 'data-service'; export function Child({ onChange }) { const callbackRef = useRef(onChange); callbackRef.current = onChange; useEffect(() => { callbackRef.current?.(createValue()); }, []); return null; }", + "expectedCount": 1, + "sourceReference": "HiddenProseMirror" + }, + { + "name": "ref-callback-body-destructure-stable-assignment", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useRef } from 'react'; import { createValue } from 'data-service'; export function Child(props) { const { onChange } = props; const callbackRef = useRef(onChange); callbackRef.current = onChange; useEffect(() => { callbackRef.current?.(createValue()); }, []); return null; }", + "expectedCount": 1, + "sourceReference": "HiddenProseMirror" + }, + { + "name": "ref-callback-body-destructure-without-assignment", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useRef } from 'react'; import { createValue } from 'data-service'; export function Child(props) { const { onChange } = props; const callbackRef = useRef(onChange); useEffect(() => { callbackRef.current?.(createValue()); }, []); return null; }", + "expectedCount": 1, + "sourceReference": "HiddenProseMirror" + }, + { + "name": "ref-callback-effect-sync-custom-hook-data", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useRef } from 'react'; import { useCells } from 'cell-store'; export function Child({ initial, onChange }) { const { cells, dimensions } = useCells({ initial }); const callbackRef = useRef(onChange); useEffect(() => { callbackRef.current = onChange; }, [onChange]); useEffect(() => { const output = {}; for (const [key, value] of cells) output[key] = value; callbackRef.current({ dimensions, cells: output }); }, [cells, dimensions]); return null; }", + "expectedCount": 1, + "sourceReference": "SpreadsheetDocumentEditor" + }, + { + "name": "object-assign-forward-ref-owner", + "ruleId": "no-pass-data-to-parent", + "source": "import { forwardRef, useEffect } from 'react'; import { readValue } from 'data-service'; export const Child = Object.assign(forwardRef((props, ref) => { const value = readValue(); useEffect(() => { props.onChange(value); }, [value, props.onChange]); return null; }), { label: 'child' });", + "expectedCount": 1, + "sourceReference": "ExcalidrawSidebar" + }, + { + "name": "forward-ref-owner-without-object-assign", + "ruleId": "no-pass-data-to-parent", + "source": "import { forwardRef, useEffect } from 'react'; import { readValue } from 'data-service'; export const Child = forwardRef((props, ref) => { const value = readValue(); useEffect(() => { props.onChange(value); }, [value, props.onChange]); return null; });", + "expectedCount": 1, + "sourceReference": "ExcalidrawSidebar" + }, + { + "name": "object-rest-props-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { readValue } from 'data-service'; export function Child({ initial, ...props }) { const value = readValue(); useEffect(() => { props.onChange(value); }, [value]); return null; }", + "expectedCount": 0, + "sourceReference": "CalcomDatePicker" + }, + { + "name": "returned-named-hook-expression-owner", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { readValue } from 'data-service'; export function createHook() { return function useValue({ onChange }) { const value = readValue(); useEffect(() => { onChange(value); }, [value, onChange]); return value; }; }", + "expectedCount": 0, + "sourceReference": "MantineStorage" + }, + { + "name": "declared-custom-hook-owner", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { readValue } from 'data-service'; export function useValue({ onChange }) { const value = readValue(); useEffect(() => { onChange(value); }, [value, onChange]); return value; }", + "expectedCount": 1, + "sourceReference": "MantineStorage" + }, + { + "name": "ordinary-helper-parent-handoff", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { readValue } from 'data-service'; export function Child({ onChange }) { const value = readValue(); const forward = next => { onChange(next); }; useEffect(() => forward(value), [value]); return null; }", + "expectedCount": 0, + "sourceReference": "QtyInputField" + }, + { + "name": "memoized-helper-parent-handoff", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect } from 'react'; import { readValue } from 'data-service'; export function Child({ onChange }) { const value = readValue(); const forward = useCallback(next => { onChange(next); }, [onChange]); useEffect(() => { forward(value); }, [value]); return null; }", + "expectedCount": 1, + "sourceReference": "QtyInputField" + }, + { + "name": "imported-array-search-logical-state-initializer", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; import { options } from 'option-config'; export function Child({ selected, onChange }) { const [value] = useState(options.find(option => option.id === selected) || options[0]); useEffect(() => { onChange(value.id); }, [value, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CalcomAlby" + }, + { + "name": "imported-array-search-direct-state-initializer", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; import { options } from 'option-config'; export function Child({ selected, onChange }) { const [value] = useState(options.find(option => option.id === selected)); useEffect(() => { onChange(value.id); }, [value, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CalcomAlby" + }, + { + "name": "namespaced-query-result-parent-logical-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { api } from 'query-api'; export function Child({ currentValue, onChange }) { const { data } = api.viewer.useQuery(); useEffect(() => { onChange(currentValue || data?.value || ''); }, [currentValue, data, onChange]); return null; }", + "expectedCount": 0, + "sourceReference": "CalcomPremiumTextfield" + }, + { + "name": "direct-query-result-parent-logical-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { useQuery } from 'query-api'; export function Child({ currentValue, onChange }) { const { data } = useQuery(); useEffect(() => { onChange(currentValue || data?.value || ''); }, [currentValue, data, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "CalcomPremiumTextfield" + }, + { + "name": "wrapper-through-data-prop-reader-without-parent-notification", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect } from 'react'; import { readValue, useLocalStore } from 'data-service'; export function Child({ options }) { const { update } = useLocalStore(); const convert = useCallback(value => options.enabled ? value.trim() : value, [options]); const forward = useCallback(value => { update(convert(value)); }, [update, convert]); useEffect(() => { forward(readValue()); }, [forward]); return null; }", + "expectedCount": 0, + "sourceReference": "CalcomDateRangeFilter" + }, + { + "name": "two-stage-parent-seeded-hook-return-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { useForm, readValue } from 'form-library'; export function Child({ initial }) { const methods = useForm({ defaultValues: initial }); const { reset } = methods; const value = readValue(); useEffect(() => { reset(value); }, [value, reset]); return null; }", + "expectedCount": 1, + "sourceReference": "PlaneIssueForm" + }, + { + "name": "direct-parent-seeded-hook-return-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; import { useForm, readValue } from 'form-library'; export function Child({ initial }) { const { reset } = useForm({ defaultValues: initial }); const value = readValue(); useEffect(() => { reset(value); }, [value, reset]); return null; }", + "expectedCount": 0, + "sourceReference": "PlaneIssueForm" + }, + { + "name": "memoized-parent-callback-literal-argument", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect } from 'react'; export function Child({ toggle }) { const setVisible = useCallback(value => { toggle(value); }, [toggle]); useEffect(() => { setVisible(false); }, [setVisible]); return null; }", + "expectedCount": 1, + "sourceReference": "PlaneResizableSidebar" + }, + { + "name": "direct-parent-callback-literal-argument", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ toggle }) { useEffect(() => { toggle(false); }, [toggle]); return null; }", + "expectedCount": 0, + "sourceReference": "PlaneResizableSidebar" + }, + { + "name": "lazy-state-parent-derived-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; import { flatten } from 'array-library'; export function Child({ items, initialId, onChange }) { const [selected] = useState(() => { if (!initialId) return null; const allItems = flatten(items); const item = allItems.find(item => item.id === initialId); return item || null; }); useEffect(() => { onChange(selected); }, [selected, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "OutlineDocumentExplorer" + }, + { + "name": "eager-state-parent-derived-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useState } from 'react'; import { flatten } from 'array-library'; export function Child({ items, onChange }) { const [selected] = useState(flatten(items)); useEffect(() => { onChange(selected); }, [selected, onChange]); return null; }", + "expectedCount": 1, + "sourceReference": "OutlineDocumentExplorer" + }, + { + "name": "wrapper-parent-notification-also-uses-react-ref", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect, useRef } from 'react'; import { createValue } from 'data-service'; export function Child({ onChange }) { const rootRef = useRef(null); const run = useCallback(value => { rootRef.current?.update(value); onChange(createValue(value)); }, [onChange]); useEffect(() => { run(createValue()); }, [run]); return null; }", + "expectedCount": 0, + "sourceReference": "PagedEditor" + }, + { + "name": "wrapper-parent-notification-without-react-ref", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect } from 'react'; import { createValue } from 'data-service'; export function Child({ onChange }) { const run = useCallback(value => { onChange(createValue(value)); }, [onChange]); useEffect(() => { run(createValue()); }, [run]); return null; }", + "expectedCount": 1, + "sourceReference": "PagedEditor" + }, + { + "name": "qualified-lazy-state-imported-transform", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import * as React from 'react'; import { flatten } from 'array-library'; export function Child({ items, onChange }) { const [selected] = React.useState(() => flatten(items)); React.useEffect(() => { onChange(selected); }, [selected]); return null; }" + }, + { + "name": "qualified-eager-state-imported-transform", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import * as React from 'react'; import { flatten } from 'array-library'; export function Child({ items, onChange }) { const [selected] = React.useState(flatten(items)); React.useEffect(() => { onChange(selected); }, [selected]); return null; }" + }, + { + "name": "guarded-effect-ref-sync-custom-hook-data", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import { useEffect, useRef } from 'react'; import { useCells } from 'cell-store'; export function Child({ initial, onChange }) { const { cells, dimensions } = useCells({ initial }); const callbackRef = useRef(onChange); useEffect(() => { callbackRef.current = onChange; }, [onChange]); const skipRef = useRef(true); useEffect(() => { if (skipRef.current) { skipRef.current = false; return; } const output = {}; for (const [key, value] of cells) output[key] = value; callbackRef.current({ dimensions, cells: output }); }, [cells, dimensions]); return null; }" + }, + { + "name": "imported-member-call-data", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import { useEffect } from 'react'; import { api } from 'query-api'; export function Child({ onChange }) { const value = api.read(); useEffect(() => { onChange(value); }, [value]); return null; }" + }, + { + "name": "imported-member-call-callback-parameter", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import { useEffect } from 'react'; import { options } from 'option-config'; export function Child({ selected, onChange }) { const value = options.find(option => option.id === selected); useEffect(() => { onChange(value); }, [value]); return null; }" + }, + { + "name": "parent-wired-hook-object-argument-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; import { useForm, readValue } from 'form-library'; export function Child({ initial }) { const methods = useForm({ defaultValues: initial }); const reset = methods.reset; useEffect(() => { reset(readValue()); }, [reset]); return null; }" + }, + { + "name": "optional status guard", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { let response; try { response = await fetch('/items'); } catch {} if (!response?.ok) throw new Error('failed'); return response.json(); }", + "expectedCount": 1 + }, + { + "name": "direct status guard", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { let response; try { response = await fetch('/items'); } catch {} if (!response.ok) throw new Error('failed'); return response.json(); }", + "expectedCount": 0 + }, + { + "name": "asserted status guard", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { const response = await fetch('/items'); if (!(response.ok as boolean)) throw new Error('failed'); return response.json(); }", + "expectedCount": 1 + }, + { + "name": "presence guard still lacks status", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { let response; try { response = await fetch('/items'); } catch {} if (!response) throw new Error('failed'); return response.json(); }", + "expectedCount": 1 + }, + { + "name": "callback before finally reset", + "ruleId": "no-loading-flag-reset-outside-finally", + "source": "export async function save() { setLoading(true); try { await request(); } finally { notify?.(false); setLoading(false); } }", + "expectedCount": 0 + }, + { + "name": "callback before catch reset", + "ruleId": "no-loading-flag-reset-outside-finally", + "source": "export async function save() { setLoading(true); try { await request(); } catch { notify?.(false); setLoading(false); } }", + "expectedCount": 1 + }, + { + "name": "throw before finally reset", + "ruleId": "no-loading-flag-reset-outside-finally", + "source": "export async function save() { setLoading(true); try { await request(); } finally { throw new Error('failed'); setLoading(false); } }", + "expectedCount": 1 + }, + { + "name": "new before finally reset", + "ruleId": "no-loading-flag-reset-outside-finally", + "source": "export async function save() { setLoading(true); try { await request(); } finally { const resource = new Resource(); setLoading(false); } }", + "expectedCount": 1 + }, + { + "name": "relative-cli-path-finds-static-id-in-another-source-file", + "ruleId": "anchor-target-exists", + "filename": "src/link.tsx", + "files": { + "package.json": "{\"name\":\"anchor-cross-file-probe\",\"private\":true,\"dependencies\":{\"react\":\"18.3.1\"}}", + "src/link.tsx": "export const Link = () => Usage;\n", + "src/target.tsx": "export const Target = () =>
Details
;\n" + }, + "expectedCount": 0 + }, + { + "name": "relative-cli-path-still-reports-missing-cross-file-id", + "ruleId": "anchor-target-exists", + "filename": "src/link.tsx", + "files": { + "package.json": "{\"name\":\"anchor-missing-target-probe\",\"private\":true,\"dependencies\":{\"react\":\"18.3.1\"}}", + "src/link.tsx": "export const Link = () => Usage;\n", + "src/target.tsx": "export const Target = () =>
Details
;\n" + }, + "expectedCount": 1 + }, + { + "name": "bare import serialization", + "ruleId": "async-parallel", + "source": "export async function load() { const first = await import('./first'); const second = await import('./second'); await import('./style.css'); return [first, second]; }", + "expectedCount": 0 + }, + { + "name": "bound imports independent", + "ruleId": "async-parallel", + "source": "export async function load() { const first = await import('./first'); const second = await import('./second'); const third = await import('./third'); return [first, second, third]; }", + "expectedCount": 1 + }, + { + "name": "bare constructor serialization", + "ruleId": "async-parallel", + "source": "export async function load() { const first = await new Request('/first'); const second = await new Request('/second'); await new Request('/third'); return [first, second]; }", + "expectedCount": 0 + }, + { + "name": "bound constructors independent", + "ruleId": "async-parallel", + "source": "export async function load() { const first = await new Request('/first'); const second = await new Request('/second'); const third = await new Request('/third'); return [first, second, third]; }", + "expectedCount": 1 + }, + { + "name": "updater aliases optional DOM lookup", + "ruleId": "no-side-effect-in-state-updater-function", + "source": "import { useState, useRef } from 'react'; export function Toggle() { const [wrapped, setWrapped] = useState(false); const elementRef = useRef(null); const toggle = () => setWrapped(previous => { const next = !previous; const element = elementRef.current?.closest('.code'); if (element) { if (next) element.setAttribute('data-wrapped', 'true'); else element.removeAttribute('data-wrapped'); } return next; }); return ; }", + "expectedCount": 1 + }, + { + "name": "updater aliases external call", + "ruleId": "no-side-effect-in-state-updater-function", + "source": "import { useState } from 'react'; export function Toggle({ getElement }) { const [wrapped, setWrapped] = useState(false); const toggle = () => setWrapped(previous => { const element = getElement(); element.setAttribute('data-wrapped', 'true'); return !previous; }); return ; }", + "expectedCount": 1 + }, + { + "name": "updater local fresh object mutation", + "ruleId": "no-side-effect-in-state-updater-function", + "source": "import { useState } from 'react'; export function Toggle() { const [wrapped, setWrapped] = useState(false); const toggle = () => setWrapped(previous => { const values = new Map(); values.set('wrapped', !previous); return !previous; }); return ; }", + "expectedCount": 0 + }, + { + "name": "fluent-on-subscribe-message", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ channel, handler }) { useEffect(() => { channel.on('change', handler).subscribe(handler); }, [channel, handler]); return null; }" + }, + { + "name": "fluent-subscribe-on-message", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ channel, handler }) { useEffect(() => { channel.subscribe(handler).on('change', handler); }, [channel, handler]); return null; }" + }, + { + "name": "separate-on-subscribe-message", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ channel, handler }) { useEffect(() => { channel.on('change', handler); channel.subscribe(handler); }, [channel, handler]); return null; }" + }, + { + "name": "fluent-subscription-channel-removal-message", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ client, handler }) { useEffect(() => { const channel = client.channel('room'); channel.on('change', {}, handler).subscribe(handler); return () => { client.removeChannel(channel); }; }, [client, handler]); return null; }" + }, + { + "name": "direct-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { const connection = new EventSource(url); return () => connection.close(); }, [url]); return null; }" + }, + { + "name": "single-helper-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { let connection = null; const connect = () => { connection = new EventSource(url); }; connect(); return () => connection?.close(); }, [url]); return null; }" + }, + { + "name": "repeated-helper-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { let connection = null; const connect = () => { connection = new EventSource(url); }; connect(); connect(); return () => connection?.close(); }, [url]); return null; }" + }, + { + "name": "deferred-helper-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { let connection = null; const connect = () => { connection = new EventSource(url); }; connect(); const timer = setTimeout(connect, 1000); return () => { clearTimeout(timer); connection?.close(); }; }, [url]); return null; }" + }, + { + "name": "guarded-reconnecting-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect, useRef } from 'react'; export function Child({ url }) { const timer = useRef(null); useEffect(() => { let connection = null; let mounted = true; const connect = () => { if (!mounted) return; connection = new EventSource(url); connection.onerror = () => { if (!mounted) return; connection?.close(); connection = null; timer.current = setTimeout(() => { if (mounted) connect(); }, 1000); }; }; connect(); return () => { mounted = false; connection?.close(); clearTimeout(timer.current); }; }, [url]); return null; }" + }, + { + "name": "single-guarded-helper-event-source-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { let connection = null; let mounted = true; const connect = () => { if (!mounted) return; connection = new EventSource(url); }; connect(); return () => { mounted = false; connection?.close(); }; }, [url]); return null; }" + }, + { + "name": "repeated-helper-with-prior-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import { useEffect } from 'react'; export function Child({ url }) { useEffect(() => { let connection = null; const connect = () => { connection?.close(); connection = new EventSource(url); }; connect(); connect(); return () => connection?.close(); }, [url]); return null; }" + }, + { + "name": "synchronous-handler-with-listener-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "synchronous-handler-with-functional-updater-and-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "synchronous-handler-with-dom-guard", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { const element = document.scrollingElement; if (!element) return; if (element.scrollTop === 0) setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "synchronous-handler-with-observer-writer", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { let visible = []; const observer = new IntersectionObserver((entries) => { visible = entries.map((entry) => entry.target.id); setSelected(visible); }); function update() { setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => { window.removeEventListener('scroll', update); observer.disconnect(); }; }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "functional-updater-without-listener", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "functional-updater-without-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); }, [items, single]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "constant-functional-updater-with-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { if (single) setSelected(items.slice(0, 1)); else setSelected(() => []); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "unknown-functional-updater-with-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => transform(current)); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "runtime-star-target-semicolonless-named-export", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './helpers'\nexport const Widget = () =>
\n", + "files": { + "package.json": "{\"name\":\"refresh-probe\",\"scripts\":{\"dev\":\"next dev\"},\"dependencies\":{\"next\":\"15.0.0\",\"react\":\"19.0.0\"}}", + "src/helpers.ts": "const helper = () => 1\nexport { helper }\n" + }, + "expectedCount": 1 + }, + { + "name": "runtime-star-target-semicolon-named-export-control", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './helpers'; export const Widget = () =>
;", + "files": { + "package.json": "{\"name\":\"refresh-probe\",\"scripts\":{\"dev\":\"next dev\"},\"dependencies\":{\"next\":\"15.0.0\",\"react\":\"19.0.0\"}}", + "src/helpers.ts": "const helper = () => 1; export { helper };" + }, + "expectedCount": 1 + }, + { + "name": "runtime-star-target-comment-before-named-export", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './helpers'; export const Widget = () =>
;", + "files": { + "package.json": "{\"name\":\"refresh-probe\",\"scripts\":{\"dev\":\"next dev\"},\"dependencies\":{\"next\":\"15.0.0\",\"react\":\"19.0.0\"}}", + "src/helpers.ts": "const helper = () => 1;\n// A named runtime export.\nexport { helper };" + }, + "expectedCount": 1 + }, + { + "name": "runtime-star-target-type-only-control", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './helpers'; export const Widget = () =>
;", + "files": { + "package.json": "{\"name\":\"refresh-probe\",\"scripts\":{\"dev\":\"next dev\"},\"dependencies\":{\"next\":\"15.0.0\",\"react\":\"19.0.0\"}}", + "src/helpers.ts": "interface Options { value: string }\nexport type { Options }\n" + }, + "expectedCount": 0 + }, + { + "name": "block-row-destructured-item-composite-key", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ items }) =>
{items.map(({ name }, index) =>

{name}

)}
;", + "expectedCount": 0 + }, + { + "name": "block-row-primitive-item-composite-key", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ items }) =>
{items.map((name, index) =>

{name}

)}
;", + "expectedCount": 0 + }, + { + "name": "block-row-bare-index-control", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ items }) =>
{items.map(({ name }, index) =>

{name}

)}
;", + "expectedCount": 1 + }, + { + "name": "block-row-composite-key-stateful-control", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ items }) =>
{items.map(({ name }, index) =>

)}
;", + "expectedCount": 1 + }, + { + "name": "ensure-map-member-key-for-of-const", + "ruleId": "no-non-null-assertion-on-maybe-undefined-result", + "source": "const grouped = new Map(); for (const item of items) { if (!grouped.has(item.group)) grouped.set(item.group, []); grouped.get(item.group)!.push('value'); }", + "expectedCount": 0 + }, + { + "name": "ensure-map-member-key-local-const-control", + "ruleId": "no-non-null-assertion-on-maybe-undefined-result", + "source": "const grouped = new Map(); const item = getItem(); if (!grouped.has(item.group)) grouped.set(item.group, []); grouped.get(item.group)!.push('value');", + "expectedCount": 1 + }, + { + "name": "ensure-map-member-key-for-of-let-control", + "ruleId": "no-non-null-assertion-on-maybe-undefined-result", + "source": "const grouped = new Map(); for (let item of items) { if (!grouped.has(item.group)) grouped.set(item.group, []); grouped.get(item.group)!.push('value'); }", + "expectedCount": 1 + }, + { + "name": "ensure-map-member-key-for-of-invalidation-control", + "ruleId": "no-non-null-assertion-on-maybe-undefined-result", + "source": "const grouped = new Map(); for (const item of items) { if (!grouped.has(item.group)) grouped.set(item.group, []); grouped.delete(item.group); grouped.get(item.group)!.push('value'); }", + "expectedCount": 1 + }, + { + "name": "async-map-conditional-bound-promise-array", + "ruleId": "async-await-in-loop", + "source": "async function load(items, enabled) { const jobs = enabled ? [] : items.map(async item => await fetch(item)); await Promise.all(jobs); }", + "expectedCount": 0 + }, + { + "name": "async-map-logical-bound-promise-array", + "ruleId": "async-await-in-loop", + "source": "async function load(items, existing) { const jobs = existing || items.map(async item => await fetch(item)); await Promise.all(jobs); }", + "expectedCount": 0 + }, + { + "name": "async-map-chained-bound-promise-array", + "ruleId": "async-await-in-loop", + "source": "async function load(items) { const jobs = items.map(async item => await fetch(item)).filter(Boolean); await Promise.all(jobs); }", + "expectedCount": 0 + }, + { + "name": "async-map-conditional-uncombined-control", + "ruleId": "async-await-in-loop", + "source": "async function load(items, enabled) { const jobs = enabled ? [] : items.map(async item => await fetch(item)); return jobs; }", + "expectedCount": 1 + }, + { + "name": "async-map-nontransparent-call-control", + "ruleId": "async-await-in-loop", + "source": "async function load(items) { await Promise.all(wrap(items.map(async item => await fetch(item)))); }", + "expectedCount": 1 + }, + { + "name": "async-map-unrelated-function-binding-control", + "ruleId": "async-await-in-loop", + "source": "async function load(items) { const jobs = items.map(async item => await fetch(item)); return jobs; } async function other(jobs) { await Promise.all(jobs); }", + "expectedCount": 1 + }, + { + "name": "optional local fresh container", + "ruleId": "no-side-effect-in-state-updater-function", + "source": "import { useState } from 'react'; const makeValues = () => new Map(); export function Child() { const [value,setValue] = useState(0); return ; }", + "expectedCount": 0 + }, + { + "name": "block row key does not own another item symbol", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ items }) =>
{items.map(({ name, value }, index) =>

{value}

)}
;", + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-const-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; const alias = isBrowser; export const View = () => alias() ?
: ;", + "files": { + "src/entry.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 0 + }, + { + "name": "hydration-imported-helper-direct-control", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-star-barrel", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export * from './helper';", + "src/helper.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-named-barrel-control", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export { isBrowser } from './helper';", + "src/helper.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-named-chain-3-files", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export { isBrowser } from './level-2';", + "src/level-2.ts": "export { isBrowser } from './level-3';", + "src/level-3.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-named-chain-4-files", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export { isBrowser } from './level-2';", + "src/level-2.ts": "export { isBrowser } from './level-3';", + "src/level-3.ts": "export { isBrowser } from './level-4';", + "src/level-4.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-imported-helper-named-chain-5-files", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export { isBrowser } from './level-2';", + "src/level-2.ts": "export { isBrowser } from './level-3';", + "src/level-3.ts": "export { isBrowser } from './level-4';", + "src/level-4.ts": "export { isBrowser } from './level-5';", + "src/level-5.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 0 + }, + { + "name": "hydration-ambiguous-star-exports", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export * from './browser'; export * from './constant';", + "src/browser.ts": "export const isBrowser = () => typeof window !== 'undefined';", + "src/constant.ts": "export const isBrowser = () => false;" + }, + "expectedCount": 0 + }, + { + "name": "hydration-same-origin-star-exports", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export * from './first'; export * from './second';", + "src/first.ts": "export { isBrowser } from './browser';", + "src/second.ts": "export { isBrowser } from './browser';", + "src/browser.ts": "export const isBrowser = () => typeof window !== 'undefined';" + }, + "expectedCount": 1 + }, + { + "name": "hydration-named-export-precedes-star", + "ruleId": "no-hydration-branch-on-browser-global", + "filename": "src/view.tsx", + "source": "import React from 'react'; import { isBrowser } from './entry'; export const View = () => isBrowser() ?
: ;", + "files": { + "src/entry.ts": "export * from './browser'; export { isBrowser } from './constant';", + "src/browser.ts": "export const isBrowser = () => typeof window !== 'undefined';", + "src/constant.ts": "export const isBrowser = () => false;" + }, + "expectedCount": 0 + }, + { + "name": "synchronous-handler-with-listener-cleanup", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "synchronous-handler-with-functional-updater-and-cleanup", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "synchronous-handler-with-dom-guard", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { const element = document.scrollingElement; if (!element) return; if (element.scrollTop === 0) setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => window.removeEventListener('scroll', update); }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "synchronous-handler-with-observer-writer", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items }) => { const [selected, setSelected] = useState([]); useEffect(() => { let visible = []; const observer = new IntersectionObserver((entries) => { visible = entries.map((entry) => entry.target.id); setSelected(visible); }); function update() { setSelected(items.slice(0, 1)); } update(); window.addEventListener('scroll', update); return () => { window.removeEventListener('scroll', update); observer.disconnect(); }; }, [items]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "functional-updater-without-listener", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "functional-updater-without-cleanup", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { function update() { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => current.length ? items.slice(items.indexOf(current[0])) : items.slice(-1)); } update(); }, [items, single]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "constant-functional-updater-with-cleanup", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { if (single) setSelected(items.slice(0, 1)); else setSelected(() => []); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 1 + }, + { + "name": "unknown-functional-updater-with-cleanup", + "ruleId": "no-derived-state-effect", + "source": "import { useEffect, useState } from 'react'; export const Preview = ({ items, single }) => { const [selected, setSelected] = useState([]); useEffect(() => { if (single) setSelected(items.slice(0, 1)); else setSelected((current) => transform(current)); return () => {}; }, [items, single]); return
{selected}
; };", + "expectedCount": 0 + }, + { + "name": "fetch-body-try-catch-exit", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { const response = await fetch('/items'); const body = await response.json(); if (!response.ok) { try { return JSON.stringify(body); } catch { return 'invalid'; } } return body; }", + "expectedCount": 0 + }, + { + "name": "fetch-body-partial-catch-exit", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { const response = await fetch('/items'); const body = await response.json(); if (!response.ok) { try { return JSON.stringify(body); } catch { console.log('invalid'); } } return body; }", + "expectedCount": 1 + }, + { + "name": "fetch-body-finally-exit", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { const response = await fetch('/items'); const body = await response.json(); if (!response.ok) { try { console.log('invalid'); } finally { return body; } } return body; }", + "expectedCount": 0 + }, + { + "name": "fetch-body-literal-branch-exit", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { const response = await fetch('/items'); const body = await response.json(); if (!response.ok) { if (true) return body; } return body; }", + "expectedCount": 0 + }, + { + "name": "fetch-body-break-exit", + "ruleId": "no-fetch-response-used-without-status-check", + "source": "export async function load() { for (const url of urls) { const response = await fetch(url); const body = await response.json(); if (!response.ok) { break; } consume(body); } }", + "expectedCount": 0 + }, + { + "name": "deferred-foreach-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const rotate = () => { items.forEach(() => { setTimeout(() => update(), 100); }); }; const interval = setInterval(rotate, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-map-collected-timers", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const handles = []; const interval = setInterval(() => { items.map(() => { handles.push(setTimeout(() => update(), 100)); }); }, 1000); return () => { clearInterval(interval); handles.forEach(clearTimeout); }; }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-nested-iterators", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => { items.map(() => { setTimeout(() => update(), 100); }); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-reduce-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.reduce(() => { setTimeout(() => update(), 100); }, null); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-array-from-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { Array.from(items, () => { setTimeout(() => update(), 100); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-find-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.find(() => { setTimeout(() => update(), 100); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 0 + }, + { + "name": "deferred-computed-foreach-timer", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items['forEach'](() => { setTimeout(() => update(), 100); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 0 + }, + { + "name": "deferred-iterator-named-callback", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const allocate = () => { setTimeout(() => update(), 100); }; const interval = setInterval(() => { items.forEach(allocate); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 0 + }, + { + "name": "deferred-iterator-local-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => { const allocate = () => { setTimeout(() => update(), 100); }; allocate(); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 0 + }, + { + "name": "deferred-iterator-second-argument", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => {}, () => { setTimeout(() => update(), 100); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 0 + }, + { + "name": "deferred-iterator-returned-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => { const timeout = setTimeout(() => update(), 100); return () => clearTimeout(timeout); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-iterator-synchronously-released", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => { const timeout = setTimeout(() => update(), 100); clearTimeout(timeout); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "deferred-iterator-cancelled-timer-callback", + "ruleId": "effect-needs-cleanup", + "source": "import { useEffect } from 'react'; export const Preview = ({ items }) => { useEffect(() => { const interval = setInterval(() => { items.forEach(() => { const timeout = setTimeout(() => { setInterval(() => update(), 100); }, 100); clearTimeout(timeout); }); }, 1000); return () => clearInterval(interval); }, [items]); return null; };", + "expectedCount": 1 + }, + { + "name": "live-async-helper-assigned-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(); useEffect(() => { const update = async () => { let value; value = state.value; onChange({ ...config, value }); }; update(); }, [state]); return null; }", + "expectedCount": 0 + }, + { + "name": "live-async-helper-direct-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(); useEffect(() => { const update = async () => { onChange({ ...config, value: state.value }); }; update(); }, [state]); return null; }", + "expectedCount": 0 + }, + { + "name": "live-async-helper-const-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(); useEffect(() => { const update = async () => { const value = state.value; onChange({ ...config, value }); }; update(); }, [state]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-local-constant-array-includes", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ items, onChange }) { useEffect(() => { const allowed = ['a', 'b']; const filtered = items.filter(item => allowed.includes(item.kind)); onChange(filtered); }, [items]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-string-read-local-constants", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ items, onChange }) { useEffect(() => { const allowed = ['a', 'b']; const filtered = items.filter(item => !(allowed.includes(item.kind) && allowed.includes(item.value.toString()))); onChange(filtered); }, [items]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-child-set-has", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ items, onChange }) { useEffect(() => { const allowed = new Set(['a', 'b']); const filtered = items.filter(item => allowed.has(item.kind)); onChange(filtered); }, [items]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-map-imported-converter", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert } from 'data-library'; export function Child({ items, onChange }) { const value = useMemo(() => items.map(item => convert(item)), [items]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-map-local-converted-return", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert, isDefined } from 'data-library'; export function Child({ items, onChange }) { const value = useMemo(() => { const filtered = items.filter(item => item.enabled); return filtered.map(item => { const extra = items.find(other => other.id === item.id); return convert(item, extra); }).filter(isDefined); }, [items]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-map-for-of-item", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert } from 'data-library'; export function Child({ items, onChange }) { const value = useMemo(() => items.map(item => convert(item)), [items]); useEffect(() => { for (const item of value) onChange(item); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-nested-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo, useState } from 'react'; import { normalize } from 'data-library'; export function Child({ items, onChange }) { const [search] = useState(''); const value = useMemo(() => { const term = normalize(search); return items.filter(item => normalize(item.label).includes(term)); }, [items, search]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-nested-local-string", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo, useState } from 'react'; export function Child({ items, onChange }) { const [search] = useState(''); const value = useMemo(() => { const term = search.trim(); return items.filter(item => item.label.includes(term)); }, [items, search]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-nested-imported-transform-chain", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo, useState } from 'react'; import { normalize } from 'data-library'; export function Child({ items, initial, onChange }) { const [search] = useState(''); const [selected] = useState(initial); const filtered = useMemo(() => { const term = normalize(search); return items.filter(item => item.value !== selected?.value && normalize(item.label).includes(term)); }, [items, search, selected]); const value = useMemo(() => selected ? [selected, ...filtered] : filtered, [selected, filtered]); useEffect(() => { onChange?.(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "live-seeded-async-helper-assigned-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(config); useEffect(() => { const update = async () => { let value; value = state.value; onChange({ ...config, value }); }; update(); }, [state]); return null; }", + "expectedCount": 0 + }, + { + "name": "live-seeded-async-helper-direct-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(config); useEffect(() => { const update = async () => { onChange({ ...config, value: state.value }); }; update(); }, [state]); return null; }", + "expectedCount": 1 + }, + { + "name": "live-seeded-async-helper-const-state", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useEffect } from 'react'; import { useRemote } from 'state-library'; export function Child({ config, onChange }) { const state = useRemote(config); useEffect(() => { const update = async () => { const value = state.value; onChange({ ...config, value }); }; update(); }, [state]); return null; }", + "expectedCount": 1 + }, + { + "name": "for-of-parent-items-data-leaf", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ items, onChange }) { useEffect(() => { for (const item of items) onChange(item); }, [items]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-local-initializer-converter", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert } from 'data-library'; export function Child({ items, onChange }) { const value = useMemo(() => { const mapped = items.map(item => convert(item)); return mapped; }, [items]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-filter-state-nested-no-local", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo, useState } from 'react'; export function Child({ items, onChange }) { const [search] = useState(''); const value = useMemo(() => items.filter(item => item.label.includes(search)), [items, search]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-local-constant-term", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; export function Child({ items, onChange }) { const value = useMemo(() => { const term = 'a'; return items.filter(item => item.label.includes(term)); }, [items]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-filter-unused-local-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo, useState } from 'react'; import { normalize } from 'data-library'; export function Child({ items, onChange }) { const [search] = useState(''); const value = useMemo(() => { const term = normalize(search); return items.filter(item => item.enabled); }, [items, search]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-unknown-member-converter", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; export function Child({ items, onChange }) { const value = useMemo(() => items.filter(item => item.label.normalize()), [items]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-logical-initializer-child-set", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect } from 'react'; export function Child({ items, onChange }) { const allowed = new Set(['a']); useEffect(() => { const value = items.filter(item => allowed.has(item.id)) || []; onChange(value); }, [items]); return null; }", + "expectedCount": 1 + }, + { + "name": "anchor-project-cache-retains-first-file-ids", + "ruleId": "anchor-target-exists", + "filename": "src/first.tsx", + "targetPath": "src", + "files": { + "src/first.tsx": "export const First = () => <>
Second;", + "src/second.tsx": "export const Second = () => <>
First;" + }, + "expectedCount": 0 + }, + { + "name": "array-key-outer-index-first-in-template", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ rows }) => rows.map((row, i) => row.values.map((value, idx) => ));", + "expectedCount": 1 + }, + { + "name": "array-key-inner-index-first-in-template", + "ruleId": "no-array-index-as-key", + "source": "export const List = ({ rows }) => rows.map((row, i) => row.values.map((value, idx) => ));", + "expectedCount": 1 + }, + { + "name": "derived-value-with-local-constant", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ price }) => { const credit = 10; const [total, setTotal] = useState(0); useEffect(() => setTotal(price - credit), [price]); return
{total}
; };", + "expectedCount": 1 + }, + { + "name": "derived-local-constant-alone-is-not-source", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = () => { const credit = 10; const [total, setTotal] = useState(0); useEffect(() => setTotal(credit), []); return
{total}
; };", + "expectedCount": 0 + }, + { + "name": "derived-reduce-basic-control", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ items }) => { const [total, setTotal] = useState(0); useEffect(() => { const sum = items.reduce((acc, item) => acc + item.price, 0); setTotal(Math.max(0, sum)); }, [items]); return
{total}
; };", + "expectedCount": 1 + }, + { + "name": "derived-reduce-module-lookup-helper", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; const findValue = (item, key) => item.columns?.find(column => column.key === key)?.value; export const View = ({ items }) => { const [total, setTotal] = useState(0); useEffect(() => { const sum = items.reduce((acc, item) => acc + findValue(item, 'price'), 0); setTotal(Math.max(0, sum)); }, [items]); return
{total}
; };", + "expectedCount": 1 + }, + { + "name": "derived-reduce-module-number-helper", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; const parsePrice = price => { const amount = parseInt(price?.toString().replace('$', '').replace(',', ''), 10); return Number.isNaN(amount) ? 0 : amount; }; export const View = ({ items }) => { const [total, setTotal] = useState(0); useEffect(() => { const sum = items.reduce((acc, item) => acc + parsePrice(item.price), 0); setTotal(Math.max(0, sum)); }, [items]); return
{total}
; };", + "expectedCount": 1 + }, + { + "name": "derived-reduce-nested-module-helpers", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; const findValue = (item, key) => item.columns?.find(column => column.key === key)?.value; const parsePrice = price => { const amount = parseInt(price?.toString().replace('$', '').replace(',', ''), 10); return Number.isNaN(amount) ? 0 : amount; }; export const View = ({ items }) => { const [total, setTotal] = useState(0); useEffect(() => { const sum = items.reduce((acc, item) => acc + parsePrice(findValue(item, 'price')), 0); setTotal(Math.max(0, sum)); }, [items]); return
{total}
; };", + "expectedCount": 1 + }, + { + "name": "derived-cleanup-owned-async-writer", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ code }) => { const [status, setStatus] = useState({ kind: 'loading' }); useEffect(() => { if (code) { setStatus({ kind: 'ready', code }); return; } let active = true; async function load() { const result = await fetch('/api'); if (!active) return; if (!result.ok) setStatus({ kind: 'error' }); } load(); return () => { active = false; }; }, [code]); return
{status.kind}
; };", + "expectedCount": 1 + }, + { + "name": "derived-async-writer-without-cleanup-control", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ code }) => { const [status, setStatus] = useState({ kind: 'loading' }); useEffect(() => { if (code) { setStatus({ kind: 'ready', code }); return; } async function load() { const result = await fetch('/api'); if (!result.ok) setStatus({ kind: 'error' }); } load(); }, [code]); return
{status.kind}
; };", + "expectedCount": 1 + }, + { + "name": "derived-cleanup-filtered-asserted-missing-parameters", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ code, session, key }) => { const [status, setStatus] = useState({ kind: 'loading' }); useEffect(() => { if (code) { setStatus({ kind: 'ready', code }); return; } const missing = [!session ? 'session' : undefined, !key ? 'key' : undefined].filter(Boolean) as string[]; if (missing.length) { setStatus({ kind: 'missing', missing }); return; } let active = true; return () => { active = false; }; }, [code, session, key]); return
{status.kind}
; };", + "expectedCount": 0 + }, + { + "name": "derived-cleanup-filtered-missing-parameters", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ code, session, key }) => { const [status, setStatus] = useState({ kind: 'loading' }); useEffect(() => { if (code) { setStatus({ kind: 'ready', code }); return; } const missing = [!session ? 'session' : undefined, !key ? 'key' : undefined].filter(Boolean); if (missing.length) { setStatus({ kind: 'missing', missing }); return; } let active = true; return () => { active = false; }; }, [code, session, key]); return
{status.kind}
; };", + "expectedCount": 0 + }, + { + "name": "derived-filtered-missing-parameters-without-cleanup", + "ruleId": "no-derived-state", + "source": "import { useEffect, useState } from 'react'; export const View = ({ code, session, key }) => { const [status, setStatus] = useState({ kind: 'loading' }); useEffect(() => { if (code) { setStatus({ kind: 'ready', code }); return; } const missing = [!session ? 'session' : undefined, !key ? 'key' : undefined].filter(Boolean); if (missing.length) { setStatus({ kind: 'missing', missing }); return; } }, [code, session, key]); return
{status.kind}
; };", + "expectedCount": 1 + }, + { + "name": "derived-bare-builtin-unknown", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult([value, Boolean]),[value]);return
{result}
}", + "expectedCount": 0 + }, + { + "name": "derived-direct-builtin-call-known", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult(Boolean(value)),[value]);return
{result}
}", + "expectedCount": 1 + }, + { + "name": "derived-namespace-call-known", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult(Math.max(0,value)),[value]);return
{result}
}", + "expectedCount": 1 + }, + { + "name": "derived-new-date-known", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult(new Date(value)),[value]);return
{result}
}", + "expectedCount": 1 + }, + { + "name": "derived-new-set-known", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult(new Set(value)),[value]);return
{result}
}", + "expectedCount": 1 + }, + { + "name": "only-export-declaration-star-d.ts", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './types.d.ts'; export const Widget = () =>
;", + "files": { + "src/types.d.ts": "export interface Options { value: string }", + "package.json": "{\"name\": \"refresh-probe\", \"scripts\": {\"dev\": \"next dev\"}, \"dependencies\": {\"next\": \"15.0.0\", \"react\": \"19.0.0\"}}" + }, + "expectedCount": 1 + }, + { + "name": "only-export-declaration-star-d.mts", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './types.d.mts'; export const Widget = () =>
;", + "files": { + "src/types.d.mts": "export interface Options { value: string }", + "package.json": "{\"name\": \"refresh-probe\", \"scripts\": {\"dev\": \"next dev\"}, \"dependencies\": {\"next\": \"15.0.0\", \"react\": \"19.0.0\"}}" + }, + "expectedCount": 1 + }, + { + "name": "only-export-declaration-star-d.cts", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from './types.d.cts'; export const Widget = () =>
;", + "files": { + "src/types.d.cts": "export interface Options { value: string }", + "package.json": "{\"name\": \"refresh-probe\", \"scripts\": {\"dev\": \"next dev\"}, \"dependencies\": {\"next\": \"15.0.0\", \"react\": \"19.0.0\"}}" + }, + "expectedCount": 1 + }, + { + "name": "derived-builtin-member-transform-unknown", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult([value,Boolean.toString()]),[value]);return
{result}
}", + "expectedCount": 0 + }, + { + "name": "derived-namespace-json-stringify-known", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect} from 'react'; export const View=({value})=>{const [result,setResult]=useState(null);useEffect(()=>setResult(JSON.stringify(value)),[value]);return
{result}
}", + "expectedCount": 1 + }, + { + "name": "only-export-oversized-target-is-conservative", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from \"./large\"; export const Widget = () =>
;", + "files": { + "package.json": "{\"scripts\": {\"dev\": \"next dev\"}, \"dependencies\": {\"next\": \"15.0.0\", \"react\": \"19.0.0\"}}", + "src/large.ts": "export default 1;" + }, + "minimumFileBytes": { + "src/large.ts": 2000001 + }, + "expectedCount": 1 + }, + { + "name": "memo-nested-transform-bare-control", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useMemo,useState} from 'react'; import {normalize} from 'data-library'; export function Child({items,onChange}) { const [search]=useState(''); const value=useMemo(()=>{const term=normalize(search);return items.filter(item=>normalize(item.label).includes(term));},[items,search]);useEffect(()=>{onChange(value)},[value]);return null;}", + "expectedCount": 1 + }, + { + "name": "memo-nested-transform-namespace", + "ruleId": "no-pass-data-to-parent", + "source": "import React,{useEffect,useState} from 'react'; import {normalize} from 'data-library'; export function Child({items,onChange}) { const [search]=useState(''); const value=React.useMemo(()=>{const term=normalize(search);return items.filter(item=>normalize(item.label).includes(term));},[items,search]);useEffect(()=>{onChange(value)},[value]);return null;}", + "expectedCount": 0 + }, + { + "name": "memo-nested-transform-renamed-import", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useMemo as memoize,useState} from 'react'; import {normalize} from 'data-library'; export function Child({items,onChange}) { const [search]=useState(''); const value=memoize(()=>{const term=normalize(search);return items.filter(item=>normalize(item.label).includes(term));},[items,search]);useEffect(()=>{onChange(value)},[value]);return null;}", + "expectedCount": 1 + }, + { + "name": "only-export-target-at-parse-limit-remains-readable", + "ruleId": "only-export-components", + "filename": "src/components.tsx", + "source": "export * from \"./large\"; export const Widget = () =>
;", + "files": { + "package.json": "{\"scripts\": {\"dev\": \"next dev\"}, \"dependencies\": {\"next\": \"15.0.0\", \"react\": \"19.0.0\"}}", + "src/large.ts": "export default 1;" + }, + "minimumFileBytes": { + "src/large.ts": 2000000 + }, + "expectedCount": 0 + }, + { + "name": "ref-handler-bag-use-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect, useRef } from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = useRef(onReady); const handle = useCallback(() => read(), []); useEffect(() => { readyRef.current({ handle, inline: () => read() }); }, [handle]); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-handler-bag-use-callback-member", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = React.useRef(onReady); const handle = React.useCallback(() => read(), []); React.useEffect(() => { readyRef.current({ handle }); }, [handle]); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-handler-bag-use-callback-with-data", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect, useRef } from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = useRef(onReady); const handle = useCallback(() => read(), []); useEffect(() => { readyRef.current({ handle, value: read() }); }, [handle]); return null; }", + "expectedCount": 1 + }, + { + "name": "ref-handler-bag-renamed-use-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback as memoize, useEffect, useRef } from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = useRef(onReady); const handle = memoize(() => read(), []); useEffect(() => { readyRef.current({ handle }); }, [handle]); return null; }", + "expectedCount": 1 + }, + { + "name": "ref-handler-bag-use-memo", + "ruleId": "no-pass-data-to-parent", + "source": "import { useMemo, useEffect, useRef } from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = useRef(onReady); const handle = useMemo(() => () => read(), []); useEffect(() => { readyRef.current({ handle }); }, [handle]); return null; }", + "expectedCount": 1 + }, + { + "name": "ref-handler-bag-callback-identifier-argument", + "ruleId": "no-pass-data-to-parent", + "source": "import { useCallback, useEffect, useRef } from 'react'; import { read } from 'data-service'; export function Child({ onReady }) { const readyRef = useRef(onReady); const work = () => read(); const handle = useCallback(work, []); useEffect(() => { readyRef.current({ handle }); }, [handle]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-namespace-nested-local-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from 'react'; import { normalize } from 'data-library'; export function Child({ items, onChange }) { const [search] = React.useState(''); const value = React.useMemo(() => { const term = normalize(search); return items.filter(item => normalize(item.label).includes(term)); }, [items, search]); React.useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-renamed-nested-local-imported-transform", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo as memoize, useState } from 'react'; import { normalize } from 'data-library'; export function Child({ items, onChange }) { const [search] = useState(''); const value = memoize(() => { const term = normalize(search); return items.filter(item => normalize(item.label).includes(term)); }, [items, search]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 1 + }, + { + "name": "memo-parent-filter-chain-nullish-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert, isDefined } from 'data-library'; export function Child({ config, onChange }) { const value = useMemo(() => { const items = config.items ?? []; const filtered = items.filter(item => item.enabled); return filtered.map(item => convert(item)).filter(isDefined); }, [config]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-filter-chain-or-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert, isDefined } from 'data-library'; export function Child({ config, onChange }) { const value = useMemo(() => { const items = config.items || []; const filtered = items.filter(item => item.enabled); return filtered.map(item => convert(item)).filter(isDefined); }, [config]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-filter-chain-child-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert, isDefined, readItems } from 'data-library'; export function Child({ config, onChange }) { const value = useMemo(() => { const items = config.items ?? readItems(); const filtered = items.filter(item => item.enabled); return filtered.map(item => convert(item)).filter(isDefined); }, [config]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-parent-filter-chain-populated-literal-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import { useEffect, useMemo } from 'react'; import { convert, isDefined } from 'data-library'; export function Child({ config, onChange }) { const value = useMemo(() => { const items = config.items ?? [{ enabled: true }]; const filtered = items.filter(item => item.enabled); return filtered.map(item => convert(item)).filter(isDefined); }, [config]); useEffect(() => { onChange(value); }, [value]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-function-parent-reader-control", + "ruleId": "no-pass-data-to-parent", + "source": "import {useMemo,useEffect} from 'react'; export function Child({onReady,onRead}) { const handle=useMemo(()=>()=>onRead(),[]); useEffect(()=>{onReady(handle)},[handle]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-function-parent-reader-bag-control", + "ruleId": "no-pass-data-to-parent", + "source": "import {useMemo,useEffect,useRef} from 'react'; export function Child({onReady,onRead}) { const readyRef=useRef(onReady); const handle=useMemo(()=>()=>onRead(),[]); useEffect(()=>{readyRef.current({handle})},[handle]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-function-parent-value-control", + "ruleId": "no-pass-data-to-parent", + "source": "import {useMemo,useEffect} from 'react'; export function Child({onReady,value}) { const handle=useMemo(()=>()=>value,[]); useEffect(()=>{onReady(handle)},[handle]); return null; }", + "expectedCount": 0 + }, + { + "name": "memo-function-mixed-parent-state-control", + "ruleId": "no-pass-data-to-parent", + "source": "import {useMemo,useEffect,useState} from 'react'; export function Child({onReady,value}) { const [count]=useState(0); const handle=useMemo(()=>()=>[value,count],[]); useEffect(()=>{onReady(handle)},[handle]); return null; }", + "expectedCount": 1 + }, + { + "name": "parent-filter-call-literal-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from 'react'; export function Child({items,onChange}) { useEffect(()=>{ const value=items.filter(item=>item.enabled)||[]; onChange(value); },[items]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-call-imported-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from 'react'; import {readItems} from 'data-library'; export function Child({items,onChange}) { useEffect(()=>{ const value=items.filter(item=>item.enabled)||readItems(); onChange(value); },[items]); return null; }", + "expectedCount": 0 + }, + { + "name": "parent-filter-call-child-set-imported-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from 'react'; import {readItems} from 'data-library'; export function Child({items,onChange}) { const selected=new Set(['a']); useEffect(()=>{ const value=items.filter(item=>selected.has(item.id))||readItems(); onChange(value); },[items]); return null; }", + "expectedCount": 1 + }, + { + "name": "parent-filter-bound-call-child-set-fallback", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from 'react'; export function Child({items,onChange}) { const selected=new Set(['a']); useEffect(()=>{ const filtered=items.filter(item=>selected.has(item.id)); const value=filtered||[]; onChange(value); },[items]); return null; }", + "expectedCount": 0 + }, + { + "name": "live-state-prop-object-object-rest-direct", + "ruleId": "no-pass-live-state-to-parent", + "source": "import { useState, useEffect } from 'react'; export default function Panel({ value = 0, ...props }) { const [state, setState] = useState(0); useEffect(() => { props.onChange(state); }, [state]); return ;};" + }, + { + "name": "upstream-rerender-computed-member", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 1, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);styles['useVariants']({value});return ;};" + }, + { + "name": "upstream-rerender-spread-member", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 0, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);styles.useVariants(...[{value}]);return ;};" + }, + { + "name": "upstream-rerender-spread-direct", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 0, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);useVariants(...[{value}]);return ;};" + }, + { + "name": "upstream-rerender-asserted-member", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 1, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);(styles.useVariants as any)({value});return ;};" + }, + { + "name": "upstream-rerender-asserted-direct", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 1, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);(useVariants as any)({value});return ;};" + }, + { + "name": "upstream-rerender-optional-member", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 0, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);styles?.useVariants({value});return ;};" + }, + { + "name": "upstream-rerender-ordinary-member", + "ruleId": "rerender-state-only-in-handlers", + "expectedCount": 1, + "source": "import {useState} from 'react';export const Preview=()=>{const [value,setValue]=useState(0);styles.pickVariants({value});return ;};" + }, + { + "name": "upstream-live-plain-memoizer", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react';const useDeepCompareMemo=(value)=>value; export const Preview=({seed,onChange})=>{const value=useDeepCompareMemo(seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 0 + }, + { + "name": "upstream-live-direct-state-memoizer", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react';const useDeepCompareMemo=(value)=>{useState(0);return value;}; export const Preview=({seed,onChange})=>{const value=useDeepCompareMemo(seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 1 + }, + { + "name": "upstream-live-nested-state-memoizer", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react';const useDeepCompareMemo=(value)=>{const unused=()=>useState(0);return value;}; export const Preview=({seed,onChange})=>{const value=useDeepCompareMemo(seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 1 + }, + { + "name": "upstream-live-nested-ref-memoizer", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react';const useDeepCompareMemo=(value)=>{const unused=()=>useRef(0);return value;}; export const Preview=({seed,onChange})=>{const value=useDeepCompareMemo(seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 0 + }, + { + "name": "upstream-live-custom-spread", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react'; export const Preview=({seed,onChange})=>{const value=useCache(...[seed]);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 1 + }, + { + "name": "upstream-live-custom-direct", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react'; export const Preview=({seed,onChange})=>{const value=useCache(seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 1 + }, + { + "name": "upstream-live-custom-computed-identifier", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react'; export const Preview=({seed,onChange})=>{const value=cache[useCache](seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 1 + }, + { + "name": "upstream-live-custom-computed-string", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useState,useRef,useEffect} from 'react'; export const Preview=({seed,onChange})=>{const value=cache['useCache'](seed);useEffect(()=>{onChange(value)},[value,onChange]);return
;};", + "expectedCount": 0 + }, + { + "name": "hydration-platform-conditional-local", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; export const readPlatform = () => {return typeof window === 'undefined' ? undefined : 'mac';}; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1 + }, + { + "name": "hydration-platform-conditional-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from './runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {return typeof window === 'undefined' ? undefined : 'mac';};" + } + }, + { + "name": "hydration-platform-conditional-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from '@/runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {return typeof window === 'undefined' ? undefined : 'mac';};", + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}" + } + }, + { + "name": "hydration-platform-guard-local", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return 'mac';}; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1 + }, + { + "name": "hydration-platform-guard-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from './runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return 'mac';};" + } + }, + { + "name": "hydration-platform-guard-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from '@/runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return 'mac';};", + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}" + } + }, + { + "name": "hydration-platform-guard-value-local", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return navigator.userAgent;}; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1 + }, + { + "name": "hydration-platform-guard-value-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from './runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return navigator.userAgent;};" + } + }, + { + "name": "hydration-platform-guard-value-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from '@/runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; return navigator.userAgent;};", + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}" + } + }, + { + "name": "hydration-platform-guard-unknown-local", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; export const readPlatform = () => {if(typeof window === 'undefined') return undefined; if(/mac/.test(navigator.userAgent)) return 'mac'; return undefined;}; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1 + }, + { + "name": "hydration-platform-guard-unknown-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from './runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; if(/mac/.test(navigator.userAgent)) return 'mac'; return undefined;};" + } + }, + { + "name": "hydration-platform-guard-unknown-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from '@/runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined') return undefined; if(/mac/.test(navigator.userAgent)) return 'mac'; return undefined;};", + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}" + } + }, + { + "name": "hydration-platform-double-guard-local", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; export const readPlatform = () => {if(typeof window === 'undefined'||!window) return undefined; if(typeof navigator === 'undefined'||!navigator) return undefined; const agent=window.navigator.userAgent.toLowerCase(); if(/mac/i.test(agent))return 'mac'; else if(/win/i.test(agent))return 'win'; else return undefined;}; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1 + }, + { + "name": "hydration-platform-double-guard-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from './runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined'||!window) return undefined; if(typeof navigator === 'undefined'||!navigator) return undefined; const agent=window.navigator.userAgent.toLowerCase(); if(/mac/i.test(agent))return 'mac'; else if(/win/i.test(agent))return 'win'; else return undefined;};" + } + }, + { + "name": "hydration-platform-double-guard-alias", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from 'react'; import { readPlatform } from '@/runtime'; export const Preview = () => {const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "expectedCount": 1, + "files": { + "src/runtime.ts": "export const readPlatform = () => {if(typeof window === 'undefined'||!window) return undefined; if(typeof navigator === 'undefined'||!navigator) return undefined; const agent=window.navigator.userAgent.toLowerCase(); if(/mac/i.test(agent))return 'mac'; else if(/win/i.test(agent))return 'win'; else return undefined;};", + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{\"@/*\":[\"src/*\"]}}}" + } + }, + { + "name": "hydration-platform-block-guards-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined'||!window) return undefined; if(typeof navigator==='undefined'||!navigator) return undefined; const agent=window.navigator.userAgent.toLowerCase(); const mac=/mac/i; const win=/win/i; if(mac.test(agent)){return 'mac';}else if(win.test(agent)){return 'win';}else{return undefined;}}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-block-guards-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined'||!window) return undefined; if(typeof navigator==='undefined'||!navigator) return undefined; const agent=window.navigator.userAgent.toLowerCase(); const mac=/mac/i; const win=/win/i; if(mac.test(agent)){return 'mac';}else if(win.test(agent)){return 'win';}else{return undefined;}};" + } + }, + { + "name": "hydration-platform-single-unknown-block-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';} return undefined;}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-single-unknown-block-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';} return undefined;};" + } + }, + { + "name": "hydration-platform-single-unknown-no-final-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';}}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-single-unknown-no-final-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';}};" + } + }, + { + "name": "hydration-platform-both-unknown-branches-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';}else{return undefined;}}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-both-unknown-branches-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return 'mac';}else{return undefined;}};" + } + }, + { + "name": "hydration-platform-both-same-branches-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return undefined;}else{return undefined;}}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-both-same-branches-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(check()){return undefined;}else{return undefined;}};" + } + }, + { + "name": "hydration-platform-unknown-before-browser-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; export const readPlatform=()=>{if(check()) return 'mac'; if(typeof window==='undefined') return undefined; return undefined;}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-unknown-before-browser-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(check()) return 'mac'; if(typeof window==='undefined') return undefined; return undefined;};" + } + }, + { + "name": "hydration-platform-guard-unknown-return-local", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(true) return compute(); return undefined;}; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};" + }, + { + "name": "hydration-platform-guard-unknown-return-relative", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from './runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "src/runtime.ts": "export const readPlatform=()=>{if(typeof window==='undefined') return undefined; if(true) return compute(); return undefined;};" + } + }, + { + "name": "hydration-platform-paths-only", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-platform-missing-package-extends", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"tsconfig/nextjs.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-platform-missing-relative-extends", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./missing.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-missing-parent-local-wins", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-inherited-paths-missing-grandparent", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./config/base.json\"}", + "config/base.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"../src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-local-paths-replace-parent", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./base.json\",\"compilerOptions\":{\"paths\":{}}}", + "base.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-local-base-anchor", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"baseUrl\":\"./src\",\"paths\":{\"@/*\":[\"*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-baseurl-no-paths", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from 'runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"baseUrl\":\"./src\"}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-baseurl-fallback-after-missing-target", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from 'runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"baseUrl\":\"./src\",\"paths\":{\"runtime\":[\"missing\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-multiple-path-patterns", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"other/*\":[\"./other/*\"],\"@/*\":[\"./missing/*\",\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-more-specific-path-precedes", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"],\"@/runtime\":[\"./missing\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-jsonc-local-paths", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{/* setup */\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"],},},}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-no-client-evidence", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": " import {readPlatform} from '@/runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-bare-unmapped", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react'; import {readPlatform} from 'runtime'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "hydration-config-barrel-under-missing-extends", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react'; import {readPlatform} from '@/entry'; export const Preview=()=>{const platform=readPlatform();return
{platform==='mac'?'Meta':'Ctrl'}
;};", + "files": { + "tsconfig.json": "{\"extends\":\"./absent.json\",\"compilerOptions\":{\"paths\":{\"@/*\":[\"./src/*\"]}}}", + "src/entry.ts": "export {readPlatform} from '@/runtime';", + "src/runtime.ts": "export const readPlatform=()=>typeof window==='undefined'?undefined:'mac';" + } + }, + { + "name": "literal-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(false);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "literal-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(false);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "direct-prop-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(initial);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "direct-prop-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(initial);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "ref-current-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(containerRef.current);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "ref-current-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(containerRef.current);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "prop-member-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(initial.value);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "prop-member-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(initial.value);},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "no-argument-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run();},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "no-argument-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) { const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run();},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "prop-hook-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) {const value=useSelector(state=>select(state, initial)); const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(value);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "prop-hook-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) {const value=useSelector(state=>select(state, initial)); const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(value);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "local-ref-plain", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) {const localRef=useRef(null); const run=useCallback(value=>onChange(value),[]); useEffect(()=>{run(localRef.current);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "local-ref-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect,useRef} from \"react\"; import {useSelector} from \"react-redux\"; import {select} from \"selectors\"; import debounce from \"lodash/debounce\"; export function Preview({onChange,initial,containerRef}) {const localRef=useRef(null); const run=useCallback(debounce(value=>onChange(value),300,{leading:true}),[]); useEffect(()=>{run(localRef.current);},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useEffect-callback", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useEffect(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useEffect-callback-alias", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const memoized=useCallback(()=>run(value),[value]);const extra=memoized;useEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useEffect-memo", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useMemo(()=>value,[value]);useEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useEffect-prop", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){useEffect(()=>{},[value]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useEffect-callback-property", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useEffect(()=>{},[extra.name]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useLayoutEffect-callback", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useLayoutEffect(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useLayoutEffect-callback-alias", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const memoized=useCallback(()=>run(value),[value]);const extra=memoized;useLayoutEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useLayoutEffect-memo", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useMemo(()=>value,[value]);useLayoutEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useLayoutEffect-prop", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){useLayoutEffect(()=>{},[value]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useLayoutEffect-callback-property", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useLayoutEffect(()=>{},[extra.name]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useInsertionEffect-callback", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useInsertionEffect(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useInsertionEffect-callback-alias", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const memoized=useCallback(()=>run(value),[value]);const extra=memoized;useInsertionEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useInsertionEffect-memo", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useMemo(()=>value,[value]);useInsertionEffect(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useInsertionEffect-prop", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){useInsertionEffect(()=>{},[value]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useInsertionEffect-callback-property", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useInsertionEffect(()=>{},[extra.name]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useMemo-callback", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useMemo(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useMemo-callback-alias", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const memoized=useCallback(()=>run(value),[value]);const extra=memoized;useMemo(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useMemo-memo", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useMemo(()=>value,[value]);useMemo(()=>{},[extra]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useMemo-prop", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){useMemo(()=>{},[value]);return null;}", + "expectedCount": 0 + }, + { + "name": "extra-useMemo-callback-property", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useMemo(()=>{},[extra.name]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useCallback-callback", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useCallback(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useCallback-callback-alias", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const memoized=useCallback(()=>run(value),[value]);const extra=memoized;useCallback(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useCallback-memo", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useMemo(()=>value,[value]);useCallback(()=>{},[extra]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useCallback-prop", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){useCallback(()=>{},[value]);return null;}", + "expectedCount": 1 + }, + { + "name": "extra-useCallback-callback-property", + "ruleId": "exhaustive-deps", + "source": "import {useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback}from'react';export function Component({value}){const extra=useCallback(()=>run(value),[value]);useCallback(()=>{},[extra.name]);return null;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-asserted-value", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue((item.value as Value));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-satisfies-value", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue((item.value satisfies Value));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-constant-assertion", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(([] as Value));},[item]);return
{value}
;}", + "expectedCount": 0 + }, + { + "name": "no-derived-state-runtime-type-name", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(Value(item.value));},[item]);return
{value}
;}", + "expectedCount": 0 + }, + { + "name": "no-derived-state-nested-object", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(({ value: item.value as Value }));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-non-null", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(item!.value!);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-updater-type", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(() => item.value as Value);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-typeof-query", + "ruleId": "no-derived-state", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(item.value as typeof fallback);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-asserted-value", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue((item.value as Value));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-satisfies-value", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue((item.value satisfies Value));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-constant-assertion", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(([] as Value));},[item]);return
{value}
;}", + "expectedCount": 0 + }, + { + "name": "no-derived-state-effect-runtime-type-name", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(Value(item.value));},[item]);return
{value}
;}", + "expectedCount": 0 + }, + { + "name": "no-derived-state-effect-nested-object", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(({ value: item.value as Value }));},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-non-null", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(item!.value!);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-updater-type", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(() => item.value as Value);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "no-derived-state-effect-typeof-query", + "ruleId": "no-derived-state-effect", + "source": "import{useState,useEffect}from'react';interface Value{label:string};export function Component({item}){const [value,setValue]=useState(null);useEffect(()=>{setValue(item.value as typeof fallback);},[item]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "derived-type-Value", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect}from'react';type Value=string[];export const Component=({item})=>{const[value,setValue]=useState([]);useEffect(()=>{setValue((item.values??[]) as Value);},[item]);return
{value}
;};", + "expectedCount": 1 + }, + { + "name": "derived-type-string", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect}from'react';type Value=string[];export const Component=({item})=>{const[value,setValue]=useState([]);useEffect(()=>{setValue((item.values??[]) as string);},[item]);return
{value}
;};", + "expectedCount": 1 + }, + { + "name": "derived-type-Array", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect}from'react';type Value=string[];export const Component=({item})=>{const[value,setValue]=useState([]);useEffect(()=>{setValue((item.values??[]) as Array);},[item]);return
{value}
;};", + "expectedCount": 1 + }, + { + "name": "derived-type-ReadonlyArray", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect}from'react';type Value=string[];export const Component=({item})=>{const[value,setValue]=useState([]);useEffect(()=>{setValue((item.values??[]) as ReadonlyArray);},[item]);return
{value}
;};", + "expectedCount": 1 + }, + { + "name": "derived-type-typeof fallback", + "ruleId": "no-derived-state", + "source": "import {useState,useEffect}from'react';type Value=string[];export const Component=({item})=>{const[value,setValue]=useState([]);useEffect(()=>{setValue((item.values??[]) as typeof fallback);},[item]);return
{value}
;};", + "expectedCount": 1 + }, + { + "name": "intrinsic-handler-wrapper", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "custom-handler-wrapper", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 0 + }, + { + "name": "intrinsic-handler-call", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "custom-handler-call", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 0 + }, + { + "name": "intrinsic-state-value", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "custom-state-value", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 0 + }, + { + "name": "intrinsic-conditional-test", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 0 + }, + { + "name": "intrinsic-conditional-branch", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "custom-conditional-branch", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "mapped-intrinsic-handler", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
{items.map(item =>
;}", + "expectedCount": 1 + }, + { + "name": "mapped-custom-handler", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
{items.map(item =>
;}", + "expectedCount": 1 + }, + { + "name": "mapped-custom-render-prop", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
{items.map(item =>
;}", + "expectedCount": 0 + }, + { + "name": "mapped-custom-inline-handler", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
{items.map(item =>
;}", + "expectedCount": 1 + }, + { + "name": "intrinsic-conditional-wrapper", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "intrinsic-asserted-conditional", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 1 + }, + { + "name": "custom-asserted-conditional", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState} from 'react'; import {useForm} from 'form-library'; export function Panel(){const [state,setState]=useState(0);const form=useForm();const handle=()=>send(state); return
setState(state+1)}>
;}", + "expectedCount": 0 + }, + { + "name": "live-state-local-rest-default-factory", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; function Panel(props=createDefaults()){const [state]=useState(0);const {label,...rest}=props;useEffect(()=>{rest.onChange(state);},[state]);return null;}", + "expectedCount": 0 + }, + { + "name": "live-state-local-rest-default-hook-parameter", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; function useCounter(props={}){const [state]=useState(0);const {label,...rest}=props;useEffect(()=>{rest.handleChange(state);},[state]);return state;}", + "expectedCount": 0 + }, + { + "name": "live-state-local-rest-default-helper", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; function Panel(props={}){const [state]=useState(0);const {label,...rest}=props;const push=()=>rest.onChange(state);useEffect(()=>{push();},[push]);return null;}", + "expectedCount": 0 + }, + { + "name": "live-state-local-rest-default-typed-parameter", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; interface Props{label?:string;onChange?:(state:number)=>void}function Panel(props:Props={}){const [state]=useState(0);const {label,...rest}=props;useEffect(()=>{rest.onChange(state);},[state]);return null;}", + "expectedCount": 0 + }, + { + "name": "live-state-local-rest-bare-parameter-control", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; function Panel(props){const [state]=useState(0);const {label,...rest}=props;useEffect(()=>{rest.onChange(state);},[state]);return null;}", + "expectedCount": 1 + }, + { + "name": "live-state-object-rest-default-parameter-control", + "ruleId": "no-pass-live-state-to-parent", + "source": "import {useEffect,useState} from 'react'; function Panel({label,...rest}={}){const [state]=useState(0);useEffect(()=>{rest.handleChange(state);},[state]);return null;}", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-self-return-unused", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); function read() { return state ? read() : ''; } return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-self-return-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); function read() { return state ? read() : ''; } return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-mutual-return-unused", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); function first() { return second(state); } function second(value) { return value ? first() : ''; } return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-mutual-return-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); function first() { return second(state); } function second(value) { return value ? first() : ''; } return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-arrow-return-unused", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); const read = () => state ? read() : ''; return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-arrow-return-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); const read = () => state ? read() : ''; return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-nested-memo-unused", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); const outer = () => { const read = useMemo(() => state, [state]); return read; }; return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-nested-memo-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo} from 'react'; export function Panel() { const [state,setState] = useState(0); const outer = () => { const read = useMemo(() => state, [state]); return read; }; return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-self-return-render-prop", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); function read() { return state ? read() : ''; } return setState(state + 1)} />; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-self-return-event-handler", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); function read() { return state ? read() : ''; } return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-self-return-effect-dependency", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); function read() { return state ? read() : ''; } useEffect(() => {}, [read]); return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-named-expression-unused", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); const read = function inner() { return state ? inner() : ''; }; return ; }", + "expectedCount": 1 + }, + { + "name": "rerender-flow-cycle-named-expression-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); const read = function inner() { return state ? inner() : ''; }; return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-mutual-iterator-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useMemo,useEffect} from 'react'; export function Panel() { const [state,setState] = useState(0); function first() { return second(state); } function second(value) { return value ? first() : ''; } return ; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-recursive-timer-rendered", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useCallback,useEffect} from 'react'; export function Panel() { const [state,setState]=useState(0); const rotate=useCallback(()=>setState(state+1),[state]); useEffect(()=>{ const schedule=()=>{setTimeout(()=>{rotate();schedule();},1000);}; schedule(); },[rotate]); return
{state}
; }", + "expectedCount": 0 + }, + { + "name": "rerender-flow-cycle-recursive-timer-hidden", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useState,useCallback,useEffect} from 'react'; export function Panel() { const [state,setState]=useState(0); const rotate=useCallback(()=>setState(state+1),[state]); useEffect(()=>{ const schedule=()=>{setTimeout(()=>{rotate();schedule();},1000);}; schedule(); },[rotate]); return
ready
; }", + "expectedCount": 0 + }, + { + "name": "ref-untouched", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); React.useEffect(() => { onChange(valueRef.current); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-mutated", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); valueRef.current = readValue(); React.useEffect(() => { onChange(valueRef.current); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-mutated-logical", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); valueRef.current = readValue(); React.useEffect(() => { onChange(valueRef.current || \"\"); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-mutated-direct-value", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); valueRef.current = readValue(); React.useEffect(() => { onChange(valueRef); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-mutated-alias", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); valueRef.current = readValue(); const snapshot = valueRef.current; React.useEffect(() => { onChange(snapshot); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "object-mutated-current", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = { current: null }; valueRef.current = readValue(); React.useEffect(() => { onChange(valueRef.current); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "child-read-still-fires", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { React.useEffect(() => { onChange(readValue()); }, []); return null; }", + "expectedCount": 1 + }, + { + "name": "ref-mutated-nested-helper", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); const update = (value) => { valueRef.current = value; }; update(readValue()); React.useEffect(() => { onChange(valueRef.current || \"\"); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-binding-reassigned", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { let valueRef = React.useRef(null); valueRef = readValue(); React.useEffect(() => { onChange(valueRef.current); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-binding-reassigned-object", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { let valueRef = React.useRef(null); valueRef = readValue(); React.useEffect(() => { onChange(valueRef); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-other-property", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); valueRef.extra = readValue(); React.useEffect(() => { onChange(valueRef.extra); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "object-mutated-width", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = {width: null}; valueRef.width = readValue(); React.useEffect(() => { onChange(valueRef.width); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "ref-aliased-mutated", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview({onChange}) { const valueRef = React.useRef(null); const alias = valueRef; alias.current = readValue(); React.useEffect(() => { onChange(alias.current); }, []); return null; }", + "expectedCount": 0 + }, + { + "name": "reducer-direct", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview(props) { const [state, dispatch] = React.useReducer(reducer, props.initial); React.useEffect(()=>{ props.onChange(state); },[state]); return null; }", + "expectedCount": 1 + }, + { + "name": "reducer-destructured", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; export function Preview(props) { const [state, dispatch] = React.useReducer(reducer, props.initial); const { value }=state; React.useEffect(()=>{ props.onChange({value}); },[value]); return null; }", + "expectedCount": 1 + }, + { + "name": "query-member-import", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({key:\"value\"}); useEffect(()=>onChange(data),[data]); return null; }", + "expectedCount": 0 + }, + { + "name": "query-member-some", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({key:\"value\"}); const hasItems=data?.items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(hasItems));},[hasItems]); return null; }", + "expectedCount": 1 + }, + { + "name": "command-wrapper", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export const usePreview=({containerRef,fetchNextPage})=>{ const run=useCallback((element)=>{ const {scrollTop}=element; if(scrollTop>10)fetchNextPage(); },[fetchNextPage]); useEffect(()=>{run(containerRef.current);},[run]); };", + "expectedCount": 0 + }, + { + "name": "notification-wrapper", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export const usePreview=({containerRef,onChange})=>{ const run=useCallback((element)=>{ const {scrollTop}=element; onChange(scrollTop); },[onChange]); useEffect(()=>{run(containerRef.current);},[run]); };", + "expectedCount": 0 + }, + { + "name": "leading-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import debounce from \"lodash/debounce\"; export function Preview({onEdit}) { const value=useSelector(state=>select(state)); const run=useCallback(debounce(index=>onEdit(index),300,{leading:true,trailing:false}),[]); useEffect(()=>{run(value);},[value]); return null; }", + "expectedCount": 1 + }, + { + "name": "trailing-debounce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import debounce from \"lodash/debounce\"; export function Preview({onEdit}) { const value=useSelector(state=>select(state)); const run=useCallback(debounce(index=>onEdit(index),300),[]); useEffect(()=>{run(value);},[value]); return null; }", + "expectedCount": 1 + }, + { + "name": "reducer-local", + "ruleId": "no-pass-data-to-parent", + "source": "import * as React from \"react\"; const reducer=(state, action)=>state; export function Preview(props) { const [state, dispatch] = React.useReducer(reducer, props.initial); const { value }=state; React.useEffect(()=>{ props.onChange({value}); },[value]); return null; }", + "expectedCount": 1 + }, + { + "name": "reducer-local-bare", + "ruleId": "no-pass-data-to-parent", + "source": "import {useReducer,useEffect} from \"react\"; const reducer=(state, action)=>state; export function Preview(props) { const [state, dispatch] = useReducer(reducer, props.initial); const { value }=state; useEffect(()=>{ props.onChange({value}); },[value]); return null; }", + "expectedCount": 1 + }, + { + "name": "prop-some", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; export function Preview({onChange,items}) { const hasItems=items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(hasItems));},[hasItems]); return null; }", + "expectedCount": 0 + }, + { + "name": "local-array-some", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; export function Preview({onChange}) { const items=[{enabled:true}]; const hasItems=items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(hasItems));},[hasItems]); return null; }", + "expectedCount": 1 + }, + { + "name": "query-member-direct-some", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({key:\"value\"}); useEffect(()=>{onChange(data.items.some(item=>item.enabled));},[data]); return null; }", + "expectedCount": 1 + }, + { + "name": "notification-wrapper-direct-param", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export const usePreview=({containerRef,onChange})=>{ const run=useCallback(element=>onChange(element),[onChange]); useEffect(()=>{run(containerRef.current);},[run]); };", + "expectedCount": 0 + }, + { + "name": "notification-wrapper-local-value", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export const usePreview=({onChange})=>{ const run=useCallback(()=>onChange(readValue()),[onChange]); useEffect(()=>{run();},[run]); };", + "expectedCount": 1 + }, + { + "name": "constant-object-field", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:null}; box.value=readValue(); useEffect(()=>{onChange(box.value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-object-whole", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:null}; box.value=readValue(); useEffect(()=>{onChange(box);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-array-index", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box=[]; box[0]=readValue(); useEffect(()=>{onChange(box[0]);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-literal-binding", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box=0; box=readValue(); useEffect(()=>{onChange(box);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-literal-alias-mutation", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:null}; const alias=box; alias.value=readValue(); useEffect(()=>{onChange(alias.value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-object-initial-child", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:readValue()}; box.value=null; useEffect(()=>{onChange(box.value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "constant-object-initial-prop", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:initial}; box.value=readValue(); useEffect(()=>{onChange(box.value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "constant-object-nested-initial-child", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={value:{nested:readValue()}}; useEffect(()=>{onChange(box.value.nested);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "constant-object-template-child", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box=`value:${readValue()}`; useEffect(()=>{onChange(box);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "constant-object-alias-reassigned", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange,initial}) { let box={}; let alias=box; alias=readValue(); useEffect(()=>{onChange(alias);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-non-forwarding", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(discard(value=>onChange(value)),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-first-unrelated-second-parent", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(value=>format(value),value=>onChange(value)),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-first-parent-second-unrelated", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(value=>onChange(value),value=>format(value)),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-non-notifying", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(value=>format(value)),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-unrelated-data-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(value=>{ readValue(); return value; }),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-returns-parent-callback", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(()=>onChange),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-handler-read-no-call", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(value=>{ const handler=onChange; return value; }),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-argument-parent", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(onChange),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-nested-generic", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(wrap(value=>onChange(value))),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-first-not-function", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const discard=callback=>()=>1; const value=readValue(); const run=useCallback(wrap(12,value=>onChange(value)),[]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "wrapper-reassigned", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const value=readValue(); let run=useCallback(wrap(value=>onChange(value)),[]); run=()=>{}; useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-plain-no-hook", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const value=readValue(); const run=wrap(value=>onChange(value)); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-hook-first-callback-no-parent", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; export function Preview({onChange}) { const value=readValue(); const run=useCallback(wrap(value=>format(value)),[onChange]); useEffect(()=>{run(value);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-query-parameter", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({});const enabled=true; const result=items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-query-constant", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({});const enabled=true; const result=items.some(()=>true); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-query-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({});const enabled=true; const result=items.some(()=>readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-query-parameter-and-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({});const enabled=true; const result=items.some(item=>item.enabled && readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-query-parent-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({});const enabled=true; const result=items.some(()=>enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-parent-parameter", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const enabled=true; const result=items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-parent-constant", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const enabled=true; const result=items.some(()=>true); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-parent-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const enabled=true; const result=items.some(()=>readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-parent-parameter-and-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const enabled=true; const result=items.some(item=>item.enabled && readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-parent-parent-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const enabled=true; const result=items.some(()=>enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-local-parameter", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const items=[{enabled:true}];const enabled=true; const result=items.some(item=>item.enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-local-constant", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const items=[{enabled:true}];const enabled=true; const result=items.some(()=>true); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-local-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const items=[{enabled:true}];const enabled=true; const result=items.some(()=>readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-local-parameter-and-opaque", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const items=[{enabled:true}];const enabled=true; const result=items.some(item=>item.enabled && readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-local-parent-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const items=[{enabled:true}];const enabled=true; const result=items.some(()=>enabled); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "iterator-query-map", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({}); const result=items.map(item=>item.enabled); useEffect(()=>{onChange(result);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-query-filter", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({}); const result=items.filter(item=>item.enabled); useEffect(()=>{onChange(result);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-query-find", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({}); const result=items.find(item=>item.enabled); useEffect(()=>{onChange(result);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-query-every", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({}); const result=items.every(item=>item.enabled); useEffect(()=>{onChange(result);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "iterator-query-reduce", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useCallback} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data:items}=client.route.useQuery({}); const result=items.reduce(item=>item.enabled); useEffect(()=>{onChange(result);},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-query-Boolean", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({}); const result=data?.items.some(()=>true); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-query-Number", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({}); const result=data?.items.some(()=>true); useEffect(()=>{onChange(Number(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-query-String", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({}); const result=data?.items.some(()=>true); useEffect(()=>{onChange(String(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-query-JSON.stringify", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const {data}=client.route.useQuery({}); const result=data?.items.some(()=>true); useEffect(()=>{onChange(JSON.stringify(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-parent-Boolean", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const result=items.some(()=>readValue()); useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-parent-Number", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const result=items.some(()=>readValue()); useEffect(()=>{onChange(Number(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-parent-String", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const result=items.some(()=>readValue()); useEffect(()=>{onChange(String(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-parent-JSON.stringify", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange,items}) { const result=items.some(()=>readValue()); useEffect(()=>{onChange(JSON.stringify(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "global-constant-Boolean", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const result=0; useEffect(()=>{onChange(Boolean(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-constant-Number", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const result=0; useEffect(()=>{onChange(Number(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-constant-String", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const result=0; useEffect(()=>{onChange(String(result));},[]); return null; }", + "expectedCount": 1 + }, + { + "name": "global-constant-JSON.stringify", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect} from \"react\"; import {client} from \"client\"; export function Preview({onChange}) { const result=0; useEffect(()=>{onChange(JSON.stringify(result));},[]); return null; }", + "expectedCount": 0 + }, + { + "name": "wrapper-second-callback-owned-data", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect} from \"react\"; export function Preview({onChange}) {const run=useCallback(wrap(()=>0,()=>onChange(readValue())),[]); useEffect(()=>{run();},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "wrapper-first-callback-owned-data", + "ruleId": "no-pass-data-to-parent", + "source": "import {useCallback,useEffect} from \"react\"; export function Preview({onChange}) {const run=useCallback(wrap(()=>onChange(readValue())),[]); useEffect(()=>{run();},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "nivo-hook-ref", + "ruleId": "no-pass-data-to-parent", + "source": "import {useRef,useEffect} from \"react\"; export function usePreview({onChange}) {const notifyRef=useRef(onChange); useEffect(()=>{notifyRef.current(readValue());},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "nivo-component-ref", + "ruleId": "no-pass-data-to-parent", + "source": "import {useRef,useEffect} from \"react\"; export function Preview({onChange}) {const notifyRef=useRef(onChange); useEffect(()=>{notifyRef.current(readValue());},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "nivo-hook-ref-alias", + "ruleId": "no-pass-data-to-parent", + "source": "import {useRef,useEffect} from \"react\"; export function usePreview({onChange}) {const notifyRef=useRef(onChange); const alias=notifyRef; useEffect(()=>{alias.current(readValue());},[]); return null;}", + "expectedCount": 0 + }, + { + "name": "nivo-hook-direct", + "ruleId": "no-pass-data-to-parent", + "source": "import {useRef,useEffect} from \"react\"; export function usePreview({onChange}) { useEffect(()=>{onChange(readValue());},[]); return null;}", + "expectedCount": 1 + }, + { + "name": "listener-local-timer-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "listener-local-timer-direct", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimeout(timer); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "listener-local-timer-no-reset", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => {}, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "listener-local-timer-no-clear-first", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "listener-local-timer-unremoved", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "listener-local-timer-ref-target", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({elementRef}) => {\n useLayoutEffect(() => {\n const element = elementRef.current; if (element == null) return; let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "listener-local-timer-conditional", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = (event) => { if (event.relatedTarget == null) { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); } };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-no-null-reset-anywhere", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null; \n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-conditional-clear-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { if (ready) clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-wrong-helper-clear", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearInterval(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-async-handler", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = async () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-async-helper", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = async () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-helper-argument", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = (handle) => { clearTimeout(handle); };\n const handleEvent = () => { clearTimer(timer); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(timer); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-helper-wrong-argument", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = (handle) => { clearTimeout(handle); };\n const handleEvent = () => { clearTimer(otherTimer); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(timer); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-conditional-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { if (ready) clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-wrong-listener-cleanup", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"resize\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-unrelated-handle-write", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n timer = otherTimer; element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-conditional-registration", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n if (ready) element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-registered-after-early-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n if (ready) return; return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-handler-escaped", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n consume(handleEvent); element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-handler-invoked-directly-too", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n handleEvent(); element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-handler-called-by-owned-listener", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const onBlur = () => handleEvent(); element.addEventListener(\"blur\", onBlur);\n return () => { clearTimer(); element.removeEventListener(\"blur\", onBlur); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-overwriting-loop", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); for (const value of values) timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-branch-overwrite-protection", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { if (ready) clearTimer(); else clearTimeout(timer); timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-live-handle-early-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { if (timer != null) return; timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-wrong-early-return", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { if (ready) return; timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-timer-named-subscribe-owner", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const unsubscribe = store.subscribe(handleEvent);\n return () => { clearTimer(); unsubscribe(); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timer-interval-owned-listener", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearInterval(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setInterval(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-interval-trigger", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const outer = setInterval(handleEvent, 100);\n return () => { clearTimer(); clearInterval(outer); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-timeout-trigger", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const outer = setTimeout(handleEvent, 100);\n return () => { clearTimer(); clearTimeout(outer); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "unowned-interval-trigger", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const outer = setInterval(handleEvent, 100);\n return () => { clearTimer(); };\n }, []);\n return null;\n};", + "expectedCount": 1 + }, + { + "name": "owned-interval-inline-invoker", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const handleEvent = () => { clearTimer(); timer = setTimeout(() => { timer = null; }, 0); };\n const outer = setInterval(() => handleEvent(), 100);\n return () => { clearTimer(); clearInterval(outer); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-listener-helper-reset-argument", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = handle => clearTimeout(handle);\n const handleEvent = () => { clearTimer(timer); timer = null; timer = setTimeout(() => { timer = null; }, 0); };\n element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(timer); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "owned-listener-nested-helper-no-protection", + "ruleId": "effect-needs-cleanup", + "source": "import { useLayoutEffect } from \"react\";\nexport const Component = ({element}) => {\n useLayoutEffect(() => {\n let timer = null;\n const clearTimer = () => { if (timer == null) return; clearTimeout(timer); timer = null; };\n const startTimer = () => { timer = setTimeout(() => { timer = null; }, 0); };\n const handleEvent = () => startTimer(); element.addEventListener(\"blur\", handleEvent);\n return () => { clearTimer(); element.removeEventListener(\"blur\", handleEvent); };\n }, []);\n return null;\n};", + "expectedCount": 0 + }, + { + "name": "null-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){let data=null;data=await readValue();onChange(data);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "null-sync", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){let data=null;data=readValue();onChange(data);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "object-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){let data={};data=await readValue();onChange(data);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "string-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){let data=\"\";data=await readValue();onChange(data);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "direct-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){const data=await readValue();onChange(data);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "only-read-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){await readValue();onChange(false);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "only-read-sync", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){async function load(){readValue();onChange(false);}const init=useEffectEvent(()=>{void load();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "nested-null-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){let data=null;data=await readValue();setData(data);}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "nested-null-sync", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){let data=null;data=readValue();setData(data);}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "nested-object-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){let data={};data=await readValue();setData(data);}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "nested-direct-await", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){const data=await readValue();setData(data);}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "nested-only-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){await readValue();}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "nested-no-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useState} from \"react\";import {readValue} from \"./api\";export function Child({onChange}){const [data,setData]=useState(null);async function load(){setData(false);}const init=useEffectEvent(()=>{async function run(){await load();onChange(false);}void run();});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "inline-imported-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue,useStore} from \"./api\";export function Child({onChange}){const init=useEffectEvent(()=>{readValue();onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "inline-imported-pure", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue,useStore} from \"./api\";export function Child({onChange}){const init=useEffectEvent(()=>{Number(false);onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "inline-no-read", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue,useStore} from \"./api\";export function Child({onChange}){const init=useEffectEvent(()=>{onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "inline-local-hook", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent} from \"react\";import {readValue,useStore} from \"./api\";export function Child({onChange}){const init=useEffectEvent(()=>{const data=useStore();onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "external-hook", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useSyncExternalStore,useRef} from \"react\";import {useMediaQuery} from \"./api\";import * as api from \"./api\";export function Child({onChange,subscribe,getSnapshot}){const init=useEffectEvent(()=>{useMediaQuery(\"x\");onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "external-state", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useSyncExternalStore,useRef} from \"react\";import {useMediaQuery} from \"./api\";import * as api from \"./api\";export function Child({onChange,subscribe,getSnapshot}){const init=useEffectEvent(()=>{useSyncExternalStore(subscribe,getSnapshot);onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 1 + }, + { + "name": "react-ref", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useSyncExternalStore,useRef} from \"react\";import {useMediaQuery} from \"./api\";import * as api from \"./api\";export function Child({onChange,subscribe,getSnapshot}){const init=useEffectEvent(()=>{useRef(null);onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "json", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useSyncExternalStore,useRef} from \"react\";import {useMediaQuery} from \"./api\";import * as api from \"./api\";export function Child({onChange,subscribe,getSnapshot}){const init=useEffectEvent(()=>{JSON.stringify(false);onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "imported-namespace", + "ruleId": "no-pass-data-to-parent", + "source": "import {useEffect,useEffectEvent,useSyncExternalStore,useRef} from \"react\";import {useMediaQuery} from \"./api\";import * as api from \"./api\";export function Child({onChange,subscribe,getSnapshot}){const init=useEffectEvent(()=>{api.readValue();onChange(false);});useEffect(()=>{init();},[]);return null;}", + "expectedCount": 0 + }, + { + "name": "alias-node-modules", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"node_modules/local/platform.ts\"]}}}", + "node_modules/local/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-declaration-first", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"types/platform.d.ts\", \"src/platform.ts\"]}}}", + "types/platform.d.ts": "export declare const isBrowser:()=>boolean;", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-empty-child-paths", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"extends\": \"./base.json\", \"compilerOptions\": {\"paths\": {}}}", + "base.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"src/platform.ts\"]}}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-exports-priority", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"platform\"]}}}", + "platform/package.json": "{\"exports\": {\"require\": \"./server.ts\", \"import\": \"./browser.ts\"}}", + "platform/server.ts": "export const isBrowser=()=>false;", + "platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-missing-first", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"missing.ts\", \"src/platform.ts\"]}}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-js-extension", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"src/platform.js\"]}}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "alias-exact-first", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"src/platform\"]}}}", + "src/platform": "export const isBrowser=()=>false;", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "package-invalid-import-index", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"platform\"]}}}", + "platform/package.json": "{\"exports\": {\"import\": \"./missing.ts\", \"require\": \"./browser.ts\"}}", + "platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";", + "platform/index.ts": "export const isBrowser=()=>false;" + } + }, + { + "name": "package-field-priority", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"platform\"]}}}", + "platform/package.json": "{\"main\": \"./server.ts\", \"module\": \"./browser.ts\"}", + "platform/server.ts": "export const isBrowser=()=>false;", + "platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "package-array-priority", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"platform\"]}}}", + "platform/package.json": "{\"exports\": [{\"require\": \"./server.ts\"}, {\"import\": \"./browser.ts\"}]}", + "platform/server.ts": "export const isBrowser=()=>false;", + "platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "unaliased-package", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"strict\": true}}", + "node_modules/@/platform/package.json": "{\"main\": \"./browser.ts\"}", + "node_modules/@/platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "nested-empty-export", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\"; import {isBrowser} from \"@/platform\"; export function Preview(){return isBrowser()?

browser

:

server

;}", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\": {\"paths\": {\"@/platform\": [\"platform\"]}}}", + "platform/package.json": "{\"exports\": {\"import\": \"\", \"default\": \"./browser.ts\"}}", + "platform/browser.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "final-hydration-config-mixed-targets", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\",3]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-nonstring-target-first", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[false,\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-malformed-unrelated-target", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"bad/*\":\"broken\",\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-numeric-baseurl", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":7,\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-numeric-jsx", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"jsx\":3,\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-numeric-strict", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"strict\":3,\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-malformed-extends", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"extends\":42,\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-array-paths", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '0';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":[[\"src/runtime\"]]}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-null-only-target", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[null]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-config-valid-control", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-specific-first", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtimeSuffix';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"jsx\":3,\"paths\":{\"@/*Suffix\":[\"src/*\"],\"@/*\":[\"src/*\"]}}}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-general-first", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react';import {readMode} from '@/runtimeSuffix';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"jsx\":3,\"paths\":{\"@/*\":[\"src/*\"],\"@/*Suffix\":[\"src/*\"]}}}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-duplicate-last-valid", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"missing/*\"],\"@/*\":[\"src/*\"]}}}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-duplicate-last-invalid", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 0, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"],\"@/*\":[3]}}}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-jsonc-mixed", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"compilerOptions\":{/* retained order */\"paths\":{\"@/*\":[3,\"src/*\",],},\"strict\":3,}}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "final-hydration-order-malformed-options-inherits", + "ruleId": "no-hydration-branch-on-browser-global", + "expectedCount": 1, + "source": "import React from 'react';import {readMode} from '@/runtime';export const Preview=()=>readMode()?
:;", + "files": { + "tsconfig.json": "{\"extends\":\"./base.json\",\"compilerOptions\":17}", + "base.json": "{\"compilerOptions\":{\"paths\":{\"@/*\":[\"src/*\"]}}}", + "src/runtime.ts": "export const readMode=()=>typeof window!=='undefined';" + } + }, + { + "name": "member", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance.autoplay.start();},[instance.autoplay]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "optional-member", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[instance?.autoplay]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "optional-computed", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[instance?.[\"autoplay\"]]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "optional-nested", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[instance?.autoplay?.enabled]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "wrapped-optional", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[(instance?.autoplay as unknown)]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "optional-call", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[instance?.getAutoplay()]);return setInstance(value)}/>;}", + "expectedCount": 1 + }, + { + "name": "no-dependency", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();},[other]);return setInstance(value)}/>;}", + "expectedCount": 1 + }, + { + "name": "guard-self-echo", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{if(instance?.autoplay)setInstance(null);},[instance?.autoplay]);return setInstance(value)}/>;}", + "expectedCount": 1 + }, + { + "name": "payload-self-write", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{instance?.autoplay?.start();setInstance(null);},[instance?.autoplay]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "nested-async-setter", + "ruleId": "rerender-state-only-in-handlers", + "source": "import {useEffect,useState} from \"react\"; export function Preview({other}){const [instance,setInstance]=useState(null);useEffect(()=>{if(instance?.autoplay)queueMicrotask(()=>setInstance(null));},[instance?.autoplay]);return setInstance(value)}/>;}", + "expectedCount": 0 + }, + { + "name": "duplicate-root-last-valid", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\":{},\"compilerOptions\":{\"baseUrl\":\"src\"}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "duplicate-root-last-empty", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\"src\"},\"compilerOptions\":{}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "duplicate-base-last-valid", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":4,\"baseUrl\":\"src\"}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "duplicate-base-last-invalid", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"baseUrl\":\"src\",\"baseUrl\":4}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "duplicate-paths-last-valid", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 1, + "files": { + "tsconfig.json": "{\"compilerOptions\":{\"paths\":{},\"paths\":{\"platform\":[\"src/platform.ts\"]}}}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "root-array", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "[{\"baseUrl\":\"src\"},null]", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "compiler-options-array", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\":[\"src\",null]}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "root-empty-array", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "[]", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "compiler-options-empty-array", + "ruleId": "no-hydration-branch-on-browser-global", + "source": "import React from \"react\";import {isBrowser} from \"platform\";export const Preview=()=>isBrowser()?
:
;", + "expectedCount": 0, + "files": { + "tsconfig.json": "{\"compilerOptions\":[]}", + "src/platform.ts": "export const isBrowser=()=>typeof window!==\"undefined\";" + } + }, + { + "name": "cleanup-paired-release-channel-direct-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); channel.subscribe(); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-conditional-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); if (ready) channel.subscribe(); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-state-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); if (channel.state === \"closed\") channel.subscribe(); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-enum-state-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); if (channel.state === STATES.closed) channel.subscribe(); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-supabase-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = supabase.channel(\"alerts\"); if (channel.state === STATES.closed) channel.subscribe(); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-subscribe-handler", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); channel.subscribe(onMessage); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-computed-subscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); channel[\"subscribe\"](); return ()=>{channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-channel-conditional-unsubscribe", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import { useEffect } from \"react\"; export function Notice({client,ready,onMessage}) {useEffect(()=>{const channel = client.channel(\"alerts\"); channel.subscribe(); return ()=>{if(ready) channel.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-no-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{const channel=client.channel('alerts');channel.subscribe();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-wrong-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{const channel=client.channel('alerts');channel.subscribe();return()=>other.unsubscribe();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-subscribe-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return()=>client.unsubscribe();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-subscribe-conditional-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return()=>{if(ready)client.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-subscribe-handler-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe(onMessage);return()=>client.unsubscribe();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-subscribe-handler-wrong-argument", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe(onMessage);return()=>client.unsubscribe(other);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-subscribe-handler-same-argument", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe(onMessage);return()=>client.unsubscribe(onMessage);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-sub-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.sub();return()=>client.unsub();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-watch-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.watch();return()=>client.unwatch();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-listen-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.listen();return()=>client.unlisten();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-on-no-handler-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.on('message');return()=>client.off('message');},[]);return null;}" + }, + { + "name": "cleanup-paired-release-generic-on-missing-handler-cleanup", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.on('message',onMessage);return()=>client.off('message');},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-handle-argument", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{const handle=client.subscribe(onMessage);return()=>client.unsubscribe(handle);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-wrong-handle-argument", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{const handle=client.subscribe(onMessage);return()=>client.unsubscribe(other);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-two-arguments", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe('event',onMessage);return()=>client.unsubscribe('event',other);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-watch-event-omitted", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.watch('event');return()=>client.unwatch();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-watch-wrong-event", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.watch('event');return()=>client.unwatch('other');},[]);return null;}" + }, + { + "name": "cleanup-paired-release-watch-matching-event", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.watch('event');return()=>client.unwatch('event');},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-close-not-paired", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return()=>client.close();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-dispose", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe(onMessage);return()=>client.dispose();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-dispose-wrong-event", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe(onMessage);return()=>client.dispose(other);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-computed-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return()=>client['unsubscribe']();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-nested-uninvoked-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return()=>{const release=()=>client.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-async-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();return async()=>{await tick();client.unsubscribe();};},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-helper-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();const release=()=>client.unsubscribe();return()=>release();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-helper-param-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();const release=(target)=>target.unsubscribe();return()=>release(client);},[]);return null;}" + }, + { + "name": "cleanup-paired-release-subscribe-same-effect-release", + "ruleId": "effect-needs-cleanup", + "expectedCount": 0, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.subscribe();client.unsubscribe();},[]);return null;}" + }, + { + "name": "cleanup-paired-release-listener-missing-event", + "ruleId": "effect-needs-cleanup", + "expectedCount": 1, + "source": "import {useEffect} from \"react\";export function Notice({client,ready,onMessage,other}){useEffect(()=>{client.addEventListener('event',onMessage);return()=>client.removeEventListener();},[]);return null;}" + }, + { + "name": "final-inline-wrapper-inline-direct-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useCallback((value)=>onChange(value),[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-getter-result", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const relay=useCallback((value)=>{const result=getValue(value);return result;},[getValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-local-destructured-getter", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(config)=>{const {getValue}=config;const relay=useCallback((value)=>{const result=getValue(value);return result;},[getValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-local-identifier-alias-getter", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const readValue=getValue;const relay=useCallback((value)=>{const result=readValue(value);return result;},[readValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-prop-collection-mutator", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(props)=>{const relay=useCallback(()=>{props.records.push({value:1});},[props.records]);useEffect(()=>{relay();},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-prop-dom-member", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({target})=>{const [position,setPosition]=useState(null);const relay=useCallback(()=>{const bounds=target.element.getBoundingClientRect();setPosition({left:bounds.left});},[target]);useEffect(()=>{relay();},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-prop-editor-member", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({editor,anchor})=>{const relay=useCallback(()=>{const root=editor.getRootElement();if(editor.isEditable())placeMarker(root,anchor,new Map());},[editor,anchor]);useEffect(()=>{relay();},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-prop-direct-method", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(props)=>{const relay=useCallback((value)=>props.onChange(value),[props.onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-local-handler-alias", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const notify=onChange;const relay=useCallback((value)=>notify(value),[notify]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-destructured-handler-alias", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(props)=>{const {onChange}=props;const relay=useCallback((value)=>onChange(value),[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-handler-parameter-alias", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange:notify})=>{const relay=useCallback((value)=>notify(value),[notify]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-handler-parameter-registration", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useCallback((value)=>deliver(onChange,value),[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-data-parameter-registration", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({settings})=>{const relay=useCallback((value)=>deliver(settings,value),[settings]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-async-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useCallback(async(value)=>onChange(value),[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-named-parent-argument", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const send=(value)=>onChange(value);const relay=useCallback(send,[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-named-parent-closure-only", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const localValue={value:1};const send=()=>onChange(localValue);const relay=useCallback(send,[onChange]);useEffect(()=>{relay();},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-named-local-alias-argument", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const notify=onChange;const send=(value)=>notify(value);const relay=useCallback(send,[notify]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-named-prop-member-argument", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(props)=>{const send=(value)=>props.onChange(value);const relay=useCallback(send,[props.onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-named-async-parent-argument", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const send=async(value)=>onChange(value);const relay=useCallback(send,[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-aliased-getter-nested-ref", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(config)=>{const {getValue}=config;const cache=useRef(new Map());const relay=useCallback((values)=>{values.forEach((value)=>{const result=getValue(value);cache.current.has(result);});},[getValue]);useEffect(()=>{relay([1]);},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-getter-nested-ref", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const cache=useRef(new Map());const relay=useCallback((values)=>{values.forEach((value)=>{const result=getValue(value);cache.current.has(result);});},[getValue]);useEffect(()=>{relay([1]);},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-getter-direct-ref", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const cache=useRef(new Map());const relay=useCallback((value)=>{const result=getValue(value);cache.current.has(result);},[getValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-getter-ref-read-only", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const cache=useRef(null);const relay=useCallback((value)=>{const result=getValue(value);return cache.current||result;},[getValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-getter-unused-nested-ref", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({getValue})=>{const cache=useRef(new Map());const relay=useCallback((value)=>{const result=getValue(value);const unused=()=>cache.current.has(result);return result;},[getValue]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-direct-handler-nested-ref", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const cache=useRef(new Map());const relay=useCallback((values)=>{values.forEach((value)=>{onChange(value);cache.current.has(value);});},[onChange]);useEffect(()=>{relay([1]);},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-memo-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useMemo(()=>(value)=>onChange(value),[onChange]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-memo-prop-member", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=(props)=>{const relay=useMemo(()=>(value)=>props.records.push(value),[props.records]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-effect-event-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useEffectEvent((value)=>onChange(value));useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-custom-wrapper-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 1, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const relay=useStableCallback((value)=>onChange(value));useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-custom-wrapper-prop-member", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({service})=>{const relay=useStableCallback((value)=>service.write(value));useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-prop-echo", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange,value})=>{const relay=useCallback((next)=>onChange(next),[onChange]);useEffect(()=>{relay(value);},[relay]);return null;};" + }, + { + "name": "final-inline-wrapper-inline-local-helper-parent-callback", + "ruleId": "no-pass-data-to-parent", + "expectedCount": 0, + "source": "import {useCallback,useMemo,useEffect,useEffectEvent,useRef,useState} from \"react\";import {useStableCallback,deliver,placeMarker} from \"./helpers\";export const Preview=({onChange})=>{const send=(value)=>onChange(value);const relay=useCallback((value)=>send(value),[send]);useEffect(()=>{relay({value:1});},[relay]);return null;};" + }, + { + "name": "final-lookup-string-default-parameter", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values, candidate='')=>values.map(value=>candidate.indexOf(value)>=0);" + }, + { + "name": "final-lookup-array-default-parameter", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values, candidate=[])=>values.map(value=>candidate.indexOf(value)>=0);" + }, + { + "name": "final-lookup-fixed-array-default-parameter", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values, candidate=['first','second'])=>values.map(value=>candidate.includes(value));" + }, + { + "name": "final-lookup-unknown-parameter", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values, candidate)=>values.map(value=>candidate.includes(value));" + }, + { + "name": "final-lookup-string-default-function", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export function inspect(values,candidate=''){for(const value of values){if(candidate.indexOf(value)>=0)console.log(value);}}" + }, + { + "name": "final-lookup-destructured-source-alias", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>{const [{source:urlSource}]=useSearch();const selected=urlSource;return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-destructured-source-direct", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>{const [{source:urlSource}]=useSearch();return values.filter(value=>urlSource.includes(value));};" + }, + { + "name": "final-lookup-destructured-unknown-alias", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values)=>{const [{source:selection}]=useSearch();const selected=selection;return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-object-string-initializer-is-not-binding-initializer", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values)=>{const {selection}=formatData();return values.filter(value=>selection.includes(value));};" + }, + { + "name": "final-lookup-array-string-initializer-is-not-binding-initializer", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values)=>{const [selection]=formatData();return values.filter(value=>selection.includes(value));};" + }, + { + "name": "final-lookup-nested-string-default", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values,{inner:{selection=''}})=>values.filter(value=>selection.includes(value));" + }, + { + "name": "final-lookup-whole-object-default-is-not-property-default", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values,{selection}='')=>values.filter(value=>selection.includes(value));" + }, + { + "name": "final-lookup-nested-object-default-is-not-property-default", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values,{inner:{selection}=''}={})=>values.filter(value=>selection.includes(value));" + }, + { + "name": "final-lookup-literal-array-variable", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>{const selected=['first','second'];return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-alias-string-default", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values, source='')=>{const selected=source;return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-alias-object-string-default", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values,{selection=''})=>{const selected=selection;return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-direct-string-return-call", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>{const selected=formatData();return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-destructured-array-source-alias", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>{const [urlSource]=useSearch();const selected=urlSource;return values.filter(value=>selected.includes(value));};" + }, + { + "name": "final-lookup-default-string-callback-param", + "ruleId": "js-set-map-lookups", + "expectedCount": 0, + "source": "export const inspect=(values)=>values.map((candidate='')=>{for(const value of values)if(candidate.includes(value))console.log(value);});" + }, + { + "name": "final-lookup-rest-from-string-result", + "ruleId": "js-set-map-lookups", + "expectedCount": 1, + "source": "export const inspect=(values)=>{const {...selection}=formatData();return values.filter(value=>selection.includes(value));};" + }, + { + "name": "ref-state-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items); const previous=useRef(items); const onApply=()=>{previous.current=[...value];}; const onEdit=next=>setValue(next); useEffect(()=>{if(previous.current!==items){setValue(items);previous.current=items;}},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-other-state-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items); const [other,setOther]=useState([]);const previous=useRef(other);const onApply=()=>{previous.current=other;}; const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-mixed-state-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items); const [other,setOther]=useState([]);const previous=useRef(value); const onApply=()=>{previous.current=other;}; const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-external-write-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {read} from \"external\"; export function Preview({items}) {const [value,setValue]=useState(items);const previous=useRef(value);const onApply=()=>{previous.current=read();};const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-state-no-independent-writer", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const previous=useRef(value);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return
{value}
;}", + "expectedCount": 0 + }, + { + "name": "destructured-state-literal", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [{sort},setValue]=useState({sort:[]});useEffect(()=>{setValue({sort:[]});},[id]);return
{sort}
;}", + "expectedCount": 1 + }, + { + "name": "destructured-state-updater", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [{sort},setValue]=useState({sort:[]});useEffect(()=>{setValue(previous=>({...previous,sort:[]}));},[id]);return
{sort}
;}", + "expectedCount": 1 + }, + { + "name": "destructured-state-updater-prop-event", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [{sort},setValue]=useState({sort:[]});const onChange=next=>setValue(previous=>({...previous,sort:next}));useEffect(()=>{setValue(previous=>({...previous,sort:items}));},[items]);return ;}", + "expectedCount": 1 + }, + { + "name": "destructured-state-updater-prop-no-event", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [{sort},setValue]=useState({sort:[]});useEffect(()=>{setValue(previous=>({...previous,sort:items}));},[items]);return
{sort}
;}", + "expectedCount": 0 + }, + { + "name": "nested-destructured-state-literal", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [{nested:{sort}},setValue]=useState({nested:{sort:[]}});useEffect(()=>{setValue({nested:{sort:[]}});},[id]);return
{sort}
;}", + "expectedCount": 1 + }, + { + "name": "array-destructured-state-literal", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [[value],setValue]=useState([]);useEffect(()=>{setValue([]);},[id]);return
{value}
;}", + "expectedCount": 1 + }, + { + "name": "empty-destructured-state-literal", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [{},setValue]=useState({});useEffect(()=>{setValue({});},[id]);return null;}", + "expectedCount": 1 + }, + { + "name": "omitted-state-literal", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({id}) {const [,setValue]=useState({});useEffect(()=>{setValue({});},[id]);return null;}", + "expectedCount": 1 + }, + { + "name": "shadowed-prop-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=({filters})=>{const convert=useCallback((input)=>{const filters=[];input.forEach(value=>filters.push(value));return filters;},[]);const [value,setValue]=useState([]);const [ready,setReady]=useState(false);useEffect(()=>{if(!ready){setValue(convert([]));setReady(true);}},[ready,convert]);return value;};", + "expectedCount": 0 + }, + { + "name": "shadowed-prop-typed-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=({filters})=>{const convert=useCallback((input:Record):unknown[]=>{const filters:unknown[]=[];Object.entries(input).forEach(([key,value])=>filters.push({key,value}));return filters;},[]);const [value,setValue]=useState([]);const [ready,setReady]=useState(false);useEffect(()=>{if(!ready){setValue(convert({}));setReady(true);}},[ready,convert]);return value;};", + "expectedCount": 0 + }, + { + "name": "unshadowed-typed-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=({filters})=>{const convert=useCallback((input:Record):unknown[]=>{const output:unknown[]=[];Object.entries(input).forEach(([key,value])=>output.push({key,value}));return output;},[]);const [value,setValue]=useState([]);const [ready,setReady]=useState(false);useEffect(()=>{if(!ready){setValue(convert({}));setReady(true);}},[ready,convert]);return value;};", + "expectedCount": 0 + }, + { + "name": "shadowed-prop-external-query-hook", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {useQueryStates} from \"nuqs\";export const usePreview=({filters})=>{const convert=useCallback(input=>{const filters=[];Object.entries(input).forEach(([key,value])=>filters.push({key,value}));return filters;},[]);const [query,setQuery]=useQueryStates({});const [value,setValue]=useState([]);const [ready,setReady]=useState(false);useEffect(()=>{if(!ready){setValue(convert(query));setReady(true);}},[ready,convert,query]);return value;};", + "expectedCount": 0 + }, + { + "name": "prop-callback-dependency-control", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=({filters})=>{const convert=useCallback(input=>filters,[]);const [value,setValue]=useState([]);const [ready,setReady]=useState(false);useEffect(()=>{if(!ready){setValue(convert({}));setReady(true);}},[ready,convert]);return value;};", + "expectedCount": 1 + }, + { + "name": "uppercase-member-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const TABLE=[[\"a\",1]].map(([key,value])=>[value,key]);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "lowercase-member-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const table=[[\"a\",1]].map(([key,value])=>[value,key]);const convert=useCallback(()=>table,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-nested-member-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const TABLE=Object.fromEntries(Object.entries({a:1}).map(([key,value])=>[value,key]));const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 1 + }, + { + "name": "uppercase-direct-wrapper-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {transform} from \"external\";export const usePreview=()=>{const TABLE=transform(value=>value);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-direct-wrapper-second-callback-dependency", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {transform} from \"external\";export const usePreview=()=>{const TABLE=transform([],value=>value);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-collection-escaped-to-wrapper", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {decorate} from \"external\";export const usePreview=()=>{const TABLE=[1].map(value=>value);decorate(TABLE);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-collection-passed-member", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import external from \"external\";export const usePreview=()=>{const TABLE=[1].map(value=>value);external.decorate(TABLE);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-callback-in-array-boundary", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const TABLE=[value=>value];const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-callback-in-object-boundary", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const TABLE={convert:value=>value};const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "uppercase-usecallback-quirk", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export const usePreview=()=>{const TABLE=useCallback(value=>value,[]);const convert=useCallback(()=>TABLE,[]);const [ready,setReady]=useState(false);useEffect(()=>{setReady(true);},[convert]);return ready;};", + "expectedCount": 0 + }, + { + "name": "ref-local-helper-state-result", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const copy=input=>[...input];const previous=useRef(copy(value));const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 1 + }, + { + "name": "ref-local-helper-ignored-state-argument", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const copy=input=>[];const previous=useRef(copy(value));const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-pure-callback-ignored-state", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const previous=useRef([1].map(()=>value));const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "ref-aliased-state-source", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const snapshot=[...value];const previous=useRef(snapshot);const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 1 + }, + { + "name": "ref-nested-state-source", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [value,setValue]=useState(items);const inner=useRef(value);const previous=useRef(inner.current);const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 1 + }, + { + "name": "ref-escaped-state-source", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; import {escape} from \"external\";export function Preview({items}) {const [value,setValue]=useState(items);const previous=useRef(value);escape(previous);const onEdit=next=>setValue(next);useEffect(()=>{if(previous.current!==items)setValue(items);},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "destructured-value-not-state-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [{sort},setValue]=useState({sort:[]});const onChange=next=>setValue({sort:next});useEffect(()=>{if(sort!==items)setValue({sort:items});},[items]);return ;}", + "expectedCount": 0 + }, + { + "name": "omitted-state-setter-not-state-guard", + "ruleId": "no-adjust-state-on-prop-change", + "source": "import {useCallback,useEffect,useRef,useState} from \"react\"; export function Preview({items}) {const [,setValue]=useState({});const onChange=next=>setValue(next);useEffect(()=>{if(setValue)setValue(items);},[items]);return