TanStack / TanStack/devtools

devtools-vite: source injection silently yields zero data-tsd-source attributes (AST child-key cache poisoned by regex literals)

Open
#523 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
499
Forks
100
Avg merge
1d 17h
Merged PRs (30d)
4

Description

TanStack Devtools version
  • @tanstack/devtools-vite@0.8.5
  • transitive @tanstack/devtools-bundler-core@0.1.3
  • @tanstack/react-devtools@0.10.12 (transitive @tanstack/devtools@0.14.2)
  • Vite 8.2.2, Astro 7.2.9 (Astro drives Vite here; I could not reproduce with plain Vite)
  • Node 26.7.0, Linux
Describe the bug and the steps to reproduce it

The "Go to Source" / source inspector feature injects zero data-tsd-source
attributes. There is no browser console warning and nothing in the Vite dev
server log, so the feature just silently does nothing.

Root cause, from reading @tanstack/devtools-bundler-core/dist/esm/ast-utils.js.
getChildKeys(node) memoises the child-bearing keys of each AST node type in a
module-level childKeysCache Map, seeded from the first instance of that
type it ever sees:

for (const key in node) {
  if (key === "type" || key === "start" || key === "end") continue;
  if (typeof node[key] === "object") keys.push(key);
}

A regex literal (/abc/g) is an oxc Literal whose value is an object, so
value is cached as a child key for the type Literal. Every subsequent
string Literal then reaches:

} else if ("type" in value) callback(value);

with value being a string, and throws:

TypeError: Cannot use 'in' operator to search for 'type' in use client
    at forEachChild (.../@tanstack/devtools-bundler-core/dist/esm/ast-utils.js:31:21)

"use client" is simply the first string literal in that particular file; the
throw happens for any string literal. addSourceToJsx() wraps its whole body in
try { ... } catch (e) { return; }, so the TypeError is swallowed and the
function returns undefined — no attributes, no log, no warning.

Because the cache is module-level and shared across every file and every call,
a single regex literal anywhere in the module graph disables source injection
for every string literal parsed afterwards. That is why this reproduces in a
real dev server but not in an isolated unit test.

Minimal reproduction

Two separate node processes, same target file (any .tsx file with JSX):

import { addSourceToJsx } from '@tanstack/devtools-bundler-core'

// Case A — fresh cache: works
addSourceToJsx(heroCode, heroPath, {})   // -> transformed, N attributes injected

// Case B — parse a regex literal first, then the same file: returns undefined
addSourceToJsx('const r = /abc/g;\nexport default r;\n', regexPath, {})
addSourceToJsx(heroCode, heroPath, {})   // -> undefined

Run the two cases in separate processes. Case A injects the attributes,
Case B returns undefined.

Observed in a real app: a component that reported 36 injected attributes in
isolation injected 0 once the dev server had processed the rest of the graph.

Expected behavior

Source injection should not depend on which files were parsed earlier. And a
failure inside addSourceToJsx should at least be logged rather than swallowed,
so the feature cannot fail completely silently.

Suggested fix

Guard the non-array branch the same way the array branch already is:

- } else if ("type" in value) callback(value);
+ } else if (typeof value === "object" && "type" in value) callback(value);

With that guard, Case B injects the attributes again. A more thorough fix would
make getChildKeys key the cache on the shape of the node (or only record
keys whose value is a node or an array of nodes) and drop the bare catch.

I am carrying this as a local patch-package patch for now.

Do you intend to try to help solve this bug with your own PR?

Maybe, I'll investigate and start debugging

Terms & Code of Conduct
  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliably reproduced in a debuggable environment, it will probably not get fixed and this issue may even be closed.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the AST traversal in @tanstack/devtools-bundler-core's ast-utils.js and the addSourceToJsx entry point. Run the two-process minimal reproduction, first confirming that a regex literal poisons later string literals. Done means source injection still adds data-tsd-source attributes after regex files are processed and failures are no longer silently invisible.

Written by the indexing model from the issue text.

Assessment

Tech stack
devtools, typescript, vite
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.