Repository navigation
fix(no-value-in-untracked): only report confirmed bQuery signals - #1
Merged
Merged
Conversation
The rule reported every `<outer identifier>.value` read inside an
untrack()/watch*() callback and suggested `.peek()`, without checking
that the identifier is a signal. `const input =
document.querySelector('input'); watch(query, () => search(input.value))`
was reported, and the suggestion rewrote it to `input.peek()`, which
throws.
The rule now reports (and suggests `.peek()`) only when scope analysis
resolves the identifier to a single `const` (or never reassigned
`let`/`var`) declaration initialised with a call to a bQuery signal
factory imported from @bquery/bquery, a module subpath or /full —
named, renamed or namespace imports, via the existing matchBQueryCall
helper. The factories are those whose published 1.17 declarations
return Signal/Computed/LinkedSignal/ReadonlySignal: signal, computed,
linkedSignal, persistedSignal, readonly (reactive); useSignal,
useComputed, useSlot (component); useCookie, reducedMotionSignal,
keyboardUserSignal, focusVisible, isActiveSignal, draggablePosition,
sortableOrder. DOM elements, Map entries, parameters, imports from
other modules, destructured bindings and unresolved globals are no
longer reported.
Updates the rule's meta.docs description, README section and tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ju3jbgag99iuxndoJTrgM
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing: 4
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
10 tasks done
JosunLP
added a commit
to bQuery/bQuery
that referenced
this pull request
Oct 21, 2026
…pendency updates (#278) ## Summary This PR covers everything that was still open after #277: - **Security:** fixes for sanitizer gaps found by the adversarial corpus from #230 and by code review. - **`create-bquery`** (#225): scaffolder under `packages/create-bquery`. It is published standalone for `npm create bquery`, and `@bquery/bquery` also ships it as its `create-bquery` bin. - **Tooling packages moved out:** `@bquery/eslint-plugin` (#228) and `@bquery/unplugin` (#224) were built here, then moved to [bQuery/eslint-plugin](https://github.com/bQuery/eslint-plugin) and [bQuery/unplugin](https://github.com/bQuery/unplugin), because they are not shipped with `@bquery/bquery`. - **Library fixes:** bugs found while writing the scaffolder templates, plus the follow-ups from a code review. - **Dev dependency updates:** this includes the majors happy-dom 21 and unplugin 3. ## Motivation Closes #224, closes #225, closes #228. The plugin code lives in its own repositories now. Each has a draft PR with the review fixes: bQuery/eslint-plugin#1 and bQuery/unplugin#1. ## Changes included **Security** (`1d8f00d`, `35a4bd3`) - **Comments in SSR sanitizing:** `sanitizeHtmlForSSR()` kept HTML comments verbatim. Browsers end a comment early at `<!-->`, `<!--->` and `--!>`, which allowed XSS through `bq-html` in SSR. Comments are now dropped. - **URL attributes in SSR sanitizing:** `sanitizeHtmlForSSR()` now checks the full URL attribute list and every `srcset` candidate, not just `href`/`src`. - **Text-only elements in the DOM backend:** - `sanitizeHtml()` drops every non-text child of elements a browser parses as text (`textarea`, `title`, `xmp`, …), comments included. - `<textarea><!--</textarea><img onerror=…>--></textarea>` still broke out through a comment under happy-dom before `35a4bd3`. - The DOM backend now also drops comments everywhere, like the other two paths. - **Tests:** the corpus has 129 payloads. The property-based pass covers all three sanitizer paths. **`create-bquery`** (#225: `3026078`, `ed47d6d`, `c454576`) - `npm create bquery@latest` with four templates: CDN, SPA, SSR (Node/Bun/Deno) and full-stack. Each ships tests wired to `@bquery/bquery/testing`. - `test/templates.test.ts` scaffolds every variant against the sources and runs its tests, type-check, SPA build and a server smoke run. - `@bquery/bquery` ships the scaffolder as its `create-bquery` bin, with `packages/create-bquery/{src,templates,package.json}` in `files`. `npx create-bquery` works wherever bQuery is installed and always matches the installed release. - The standalone package's `prepublishOnly` refuses to publish until the bQuery release its templates target is on npm. **Tooling packages moved out** (`c454576`; built in `7adf83e`, `9f4d301`, `78d0866`, `f673f0b`) - `packages/eslint-plugin` and `packages/unplugin` are removed. Their history up to `980d0cc` was imported into the new repositories, where they run standalone against the published `@bquery/bquery`. - The guides point to the new repositories, and the root `unplugin` devDependency is dropped. **Library fixes** - **SSR** (`57b846a`, `3b42593`): - Full pages keep their doctype (rebuilt, not copied) and their `<html>/<head>/<body>`, with or without a global DOM. - `renderToStream()` streams a page with `flushBoundary()` markers as one document; previously the first chunk closed `</body></html>`. - `renderToStreamSuspense()` holds the closing tags until its patches are out. - Document detection skips a BOM, whitespace and comments and accepts legacy doctypes, using a linear scanner. - **Reactive** (`184cce6`, `9f005ce`): `HttpError.response.data` carries the parsed error body. The read is bounded: 1 MiB, within the remaining `timeout` (5 s when none is set), and abortable. A never-ending error body can no longer hang the request. `onDownloadProgress` reports successful bodies only. - **View** (`7c31c4a`, `ad85e44`): - `bq-on` accepts `;`-separated statements. - The compiler keys compiled code by mode: `registerCompiledExpressions(expressions, statements?)`. A source shared between `bq-on` and another directive now compiles the same way it runs, and the handler stays CSP-safe. - `view/compiler` no longer bundles the runtime evaluator. - **Store** (`e6ce336`): `createStore` / `defineStore` / `createPersistedStore` infer getters and actions. - **Testing** (`f2bf0de`): - `*ByRole({ name })`. - Real `MouseEvent` / `KeyboardEvent` / … objects from `fireEvent` / `userEvent`. - `getByLabelText` finds nested label text. - **Budgets** (`980d0cc`): `./ssr` 39.8 kB, `./server` 42.3 kB, `./testing` 9.6 kB, each with a note. The docs size table is refreshed. **Docs, CHANGELOG, CI** (`22cee8d`, `9137586`): "Tooling" sidebar group, cross-links, CHANGELOG entries, and `test:types` type-checks `packages/`. **Dependencies** (`01dd1e0`) - In range: storybook 10.6.3, @typescript-eslint 8.72.0, eslint 10.13.0, prettier 3.9.10, vite 8.3.5, bun-types 1.4.3, @playwright/test 1.64.1. - Majors: happy-dom 21.0.4, unplugin 3.4.0. The unplugin devDependency is now dropped again, since the plugin left the repo. - **TypeScript stays at 6.0.3.** TypeScript 7 drops the JS compiler API that typescript-eslint, typedoc and two of our generators depend on. ## Validation - [x] `bun run lint`, `bunx tsc --noEmit`, `bun run test:types`, `bun run format:check` - [x] `CI=true bun test`: 4664 pass, 0 fail - [x] `bun run build`, `check:size` (all 25 entries within budget), `check:package` (1.49 MB packed) - [x] `bun run check` - [x] `npx create-bquery` from the repo root scaffolds a project; `packages/create-bquery/test/bin.test.ts` covers the bin - [x] `bun run build:docs` passed in CI on the previous head ## Checklist - [x] I followed the contributing guidelines. - [x] I added or updated tests where behavior changed. - [x] I updated documentation when public behavior or developer workflow changed. - [x] I confirmed the change does not introduce security-sensitive regressions. ## Notes for reviewers - The security fixes become public with this PR. That was a deliberate decision by the maintainer. - **Publishing:** `create-bquery` (standalone), `@bquery/eslint-plugin` and `@bquery/unplugin` each need an npm Trusted Publisher. The two plugin repositories have a manual `publish.yml` for that. - **Scaffolder versions:** `BQUERY_VERSION` must equal the root version (a test enforces it), so the bundled scaffolder always targets the release it ships in. - **Frozen grammar:** `bq-on` statement lists widen the expression grammar of a frozen directive. No directive is added. - **Testing behaviour change:** clicks are real `MouseEvent`s now, so happy-dom runs default actions (submit, checkbox toggle), as a browser does. - **History:** the source deletion of the two plugin packages landed in `35a4bd3` together with the sanitizer fix; the rest of the move is `c454576`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018Ju3jbgag99iuxndoJTrgM --------- Co-authored-by: Claude <noreply@anthropic.com>
JosunLP
marked this pull request as ready for review
October 21, 2026 23:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
no-value-in-untrackedreported every<identifier>.valueread insideuntrack()/watch*()callbacks and suggested.peek(), without checking that the identifier is a signal.const input = document.querySelector('input'); watch(query, () => search(input.value));was reported, and the suggestion rewrote it toinput.peek(), which throws.The rule now reports only when scope analysis resolves the identifier to a declaration initialised from a bQuery signal factory, imported from
@bquery/bquery, a subpath or/full. Renamed and namespace imports count. The factories aresignal,computed,linkedSignal,persistedSignal,readonly,useSignal,useComputed,useSlot,useCookie,reducedMotionSignal,keyboardUserSignal,focusVisible,isActiveSignal,draggablePositionandsortableOrder. Unresolved identifiers and anything else are left alone.This is the first change after the plugin moved here from bQuery/bQuery#278 (
mainholds that import).Changes
src/rules/no-value-in-untracked.js: resolves the declaration and matches its initialiser against the factory list with the existingmatchBQueryCall.asand!wrappers are unwrapped, and reassignedlet/varbindings are skipped.input.value, a Map entry, a plain object, a function parameter, an import from./state, a non-bQuerysignal, bQueryeffect(), a reassignedlet, a destructured binding./full,useCookie.Validation
bun run typecheck,bun run lint,bun run format:checkCI=true bun test: 108 passbun run check:export-map: up to date, 1145 exports from@bquery/bquery@1.17.2Notes
mainis generated from the published@bquery/bquery@1.17.2. It no longer lists the 8 names that only exist unreleased in bQuerydev(createSchemaForm,useSchemaForm,validate, …); they come back when that release is installed and the map is regenerated.meta.docs.urlpoints to this repository's README. The bquery.js.org guide still has the full rule docs, soDOCS_BASEinsrc/utils.jscould point there instead.no-dynamic-trusted-htmlstill flagsconst html = sanitizeHtml(x); trusted(html), because it only accepts a direct sanitizer call.bQuery/eslint-pluginandpublish.yml(a manual workflow, dry-run by default).Generated by Claude Code