emberjs/ember.js · error · TraversalError

Replacing and removing in key handlers is not yet supported.

Error message

Replacing and removing in key handlers is not yet supported.

What it means

During traversal, a visitor's key 'enter' handler returned a value, meaning it tried to replace or remove the node. Traversal keyed handlers (per-property) don't support replacement/removal yet, so visitKey throws. This is a visitor API limitation, not template syntax.

Source

Thrown at packages/@glimmer/syntax/lib/traversal/traverse.ts:182

  let value = get(node, key);
  if (!value) {
    return;
  }

  let keyEnter: KeyHandler<N, VisitorKeys[N['type']] & keyof N> | undefined;
  let keyExit: KeyHandler<N, VisitorKeys[N['type']] & keyof N> | undefined;

  if (handler !== undefined) {
    let keyHandler = getKeyHandler(handler, key);
    if (keyHandler !== undefined) {
      keyEnter = getEnterFunction(keyHandler);
      keyExit = getExitFunction(keyHandler);
    }
  }

  if (keyEnter !== undefined) {
    if (keyEnter(node, key) !== undefined) {
      throw cannotReplaceOrRemoveInKeyHandlerYet(node, key);
    }
  }

  if (Array.isArray(value)) {
    visitArray(visitor, value, path, key);
  } else {
    let keyPath = new WalkerPath(value, path, key);
    let result = visitNode(visitor, keyPath);
    if (result !== undefined) {
      // TODO: dynamically check the results by having a table of
      // expected node types in value space, not just type space

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      assignKey(node, key, value, result as any);
    }
  }

  if (keyExit !== undefined) {

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Return undefined from the key handler; perform replacement in the node-level handler instead
  2. Use the node handler (per node type) where replacement/removal IS supported
  3. Mutate the node in place rather than returning a replacement

Example fix

// before
visit(node, { keys: { parts: (node, key) => [] } }) // returns value -> throws
// after
visit(node, { Block: (node) => ({ ...node, parts: [] }) }) // replace via node handler
Defensive patterns

Strategy: type-guard

Validate before calling

// assert key handlers never return a value
function safeKeyHandler(fn) { return (node, key) => { const r = fn(node, key); if (r !== undefined) throw new Error('key handlers cannot replace/remove: return undefined'); }; }

Type guard

const returnsNothing = (fn) => fn.length >= 0 && undefined; // wrap: (node,key)=>{ fn(node,key); return undefined; }

Try / catch

try { visit(ast, visitor) } catch (e) { if (/Replacing and removing in key handlers/.test(e.message)) { /* fall back to node-level handler transform */ } else throw e; }

Prevention

When it happens

Trigger: Writing a NodeVisitor whose handler for a specific key (via #visit in the traversal API, e.g. visiting Key ASTv1 nodes keyed by property) returns a non-undefined value from the key enter callback.

Common situations: Writing codemods/transformers over Glimmer ASTs; assuming visitor return values replace nodes like in other AST tools (e.g. babel); porting node-level visitors to key-level handlers.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/1afd5c5b5b6c2dad. Report an issue: GitHub.