{"id":"6d8e1ae98f5004d1","repo":"babel/babel","slug":"visitor-key-may-only-have-enter-and-or-ex","errorCode":null,"errorMessage":".visitor[\"${key}\"] may only have .enter and/or .exit handlers.","messagePattern":"\\.visitor\\[\"(.+?)\"\\] may only have \\.enter and/or \\.exit handlers\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/babel-core/src/config/validation/plugins.ts","lineNumber":67,"sourceCode":"    if (obj.enter || obj.exit) {\n      throw new Error(\n        `${msg(\n          loc,\n        )} cannot contain catch-all \"enter\" or \"exit\" handlers. Please target individual nodes.`,\n      );\n    }\n  }\n  return obj as Visitor;\n}\n\nfunction assertVisitorHandler(\n  key: string,\n  value: unknown,\n): asserts value is VisitorHandler {\n  if (value && typeof value === \"object\") {\n    Object.keys(value).forEach((handler: string) => {\n      if (handler !== \"enter\" && handler !== \"exit\") {\n        throw new Error(\n          `.visitor[\"${key}\"] may only have .enter and/or .exit handlers.`,\n        );\n      }\n    });\n  } else if (typeof value !== \"function\") {\n    throw new Error(`.visitor[\"${key}\"] must be a function`);\n  }\n}\n\ntype VisitorHandler =\n  | Function\n  | {\n      enter?: Function;\n      exit?: Function;\n    };\n\nexport type PluginObject<S extends PluginPass = PluginPass> = {\n  name?: string;","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/babel/babel/blob/06b6eae39da4ce0689fad64e2c48a6375a464208/packages/babel-core/src/config/validation/plugins.ts#L49-L85","documentation":"Thrown by assertVisitorHandler when a per-node visitor object (e.g. visitor.Identifier) contains a property other than 'enter' or 'exit'. The per-node value may be either a single function or an object with exactly the enter/exit keys; any other key is rejected because Babel's traversal has no defined behaviour for it.","triggerScenarios":"Writing visitor: { Identifier: { enter(){}, shouldStop: true } } or visitor: { Identifier: { visit(){}, exit(){} } } where 'shouldStop'/'visit' are not valid handler names. The loop at plugins.ts:64-71 throws on the first invalid handler key.","commonSituations":"Trying to attach metadata to a visitor object; confusing Babel's visitor schema with another library's; using 'visit' instead of 'enter' (Babel uses enter/exit, not visit).","solutions":["Keep only 'enter' and/or 'exit' inside per-node visitor objects.","Store any extra metadata on the plugin's state object (PluginPass) or a closure variable, not on the visitor.","Rename a 'visit' method to 'enter' if that was the intent."],"exampleFix":"// before\nvisitor: { Identifier: { visit(path){...}, meta: true } }\n// after\nvisitor: { Identifier: { enter(path){...} } }","handlingStrategy":"type-guard","validationCode":"function assertVisitorHandlers(visitor) {\n  for (const [node, handler] of Object.entries(visitor)) {\n    if (handler && typeof handler === 'object') {\n      for (const k of Object.keys(handler)) {\n        if (k !== 'enter' && k !== 'exit') {\n          throw new Error(`visitor.${node} has invalid handler '${k}'`);\n        }\n      }\n    }\n  }\n}","typeGuard":"import type { Visitor } from '@babel/traverse';\n// Visitor<S> already restricts per-node objects to { enter?, exit? }","tryCatchPattern":"try { transformSync(code, { plugins: [plugin] }); } catch (e) {\n  if (String(e.message).includes('may only have .enter and/or .exit')) {\n    throw new Error('Plugin visitor has an unknown handler key');\n  }\n  throw e;\n}","preventionTips":["Type-check plugins with Visitor<S> from @babel/traverse.","Store plugin metadata on state/closures, not on the visitor object.","Remember Babel uses enter/exit, not visit."],"tags":["plugin","visitor","validation"],"analyzedSha":"06b6eae39da4ce0689fad64e2c48a6375a464208","analyzedAt":"2026-08-03T20:13:43.465Z","schemaVersion":2}