denoland/deno · error · Error

Visitor "${name}" of plugin "${id}" errored

Error message

Visitor "${name}" of plugin "${id}" errored

What it means

Thrown at lint-run time in runPluginsForFile (cli/js/40_lint.js) when a rule's visitor callback raises. The linter wraps every enter/exit visitor so the error names the rule and plugin ("Visitor \"<rule>\" of plugin \"<plugin>/<rule>\" errored") and attaches the original exception as `cause`. This is a wrapper: the real bug is in your visitor code, retrievable via err.cause.

Source

Thrown at cli/js/40_lint.js:1285

          const key = selectors[j];

          let info = bySelector.get(key);
          if (info === undefined) {
            info = { enter: NOOP, exit: NOOP };
            bySelector.set(key, info);
          }
          const prevFn = isExit ? info.exit : info.enter;

          /**
           * @param {*} node
           */
          const wrapped = (node) => {
            prevFn(node);

            try {
              fn(node);
            } catch (err) {
              throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
                cause: err,
              });
            }
          };

          if (isExit) {
            info.exit = wrapped;
          } else {
            info.enter = wrapped;
          }
        }
      }

      if (typeof rule.destroy === "function") {
        const destroyFn = rule.destroy.bind(rule);
        destroyFns.push(() => {
          try {
            destroyFn(ruleCtx);

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Inspect err.cause (and its stack) — it points at the exact line in your visitor, not at this wrapper message
  2. Narrow inside the visitor: check node.type / property existence before accessing, or tighten the selector so only matching shapes reach the callback
  3. Reproduce with `deno lint` on the single file that triggered it, then add that file as a fixture for the rule

Example fix

// before
"CallExpression:exit"(node) {
  reportIfUpperCase(node.callee.name);
},

// after
"CallExpression:exit"(node) {
  if (node.callee.type !== "Identifier") return;
  reportIfUpperCase(node.callee.name);
},
Defensive patterns

Strategy: try-catch

Validate before calling

// smoke-test the plugin in CI: lint a fixture that exercises every visitor key
// $ deno lint --config deno.json ./plugin_tests/fixtures/edge_cases.ts

Try / catch

try {
  await runLint(fixtures); // deno lint over representative files
} catch (err) {
  // the wrapper names the rule; err.cause holds the real stack
  if (err instanceof Error && /Visitor ".+" of plugin ".+" errored/.test(err.message)) {
    console.error("rule bug:", err.cause ?? err);
  }
  throw err;
}

Prevention

When it happens

Trigger: Any exception inside a `create(ctx)` visitor function while traversing a file: calling a method on undefined (e.g. node.callee.name on a node without callee), accessing properties of a node type that doesn't have them, or throwing intentionally without wrapping. Fires per file visited, during `deno lint` on real source files (not at plugin load time).

Common situations: A visitor assumes a specific node shape (e.g. MemberExpression) but the selector also matched a different shape; a rule that reads node.parent or sibling nodes that can be null; rules tested only on simple files crashing on edge-case syntax (decorators, optional chaining, directives).

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/bff530b590f77598. Report an issue: GitHub.