tree-sitter/tree-sitter · error · Error

Rule '${ruleName}' returned undefined.

Error message

Rule '${ruleName}' returned undefined.

What it means

Thrown by `grammar({...})` when a rule function is invoked and returns `undefined`. Every rule must evaluate to a rule value (string, RegExp, symbol, or a helper's node), which is then passed to `normalize()`. The classic cause is a block-bodied function or arrow missing its `return`, or a conditional body where one branch returns nothing. The message names the rule, and the thrown location is inside dsl.js, so the stack's grammar.js frame is what points at the offending function.

Source

Thrown at crates/generate/src/dsl.js:331

  if (inherits && !/^[a-zA-Z_]\w*$/.test(name)) {
    throw new Error("Base grammar's 'name' property must not start with a digit and cannot contain non-word characters.");
  }

  const rules = Object.assign({}, baseGrammar.rules);
  if (options.rules) {
    if (typeof options.rules !== "object") {
      throw new Error("Grammar's 'rules' property must be an object.");
    }

    for (const ruleName of Object.keys(options.rules)) {
      const ruleFn = options.rules[ruleName];
      if (typeof ruleFn !== "function") {
        throw new Error(`Grammar rules must all be functions. '${ruleName}' rule is not.`);
      }
      const rule = ruleFn.call(ruleBuilder, ruleBuilder, baseGrammar.rules[ruleName]);
      if (rule === undefined) {
        throw new Error(`Rule '${ruleName}' returned undefined.`);
      }
      rules[ruleName] = normalize(rule);
    }
  }

  let reserved = baseGrammar.reserved;
  if (options.reserved) {
    if (typeof options.reserved !== "object") {
      throw new Error("Grammar's 'reserved' property must be an object.");
    }

    for (const reservedWordSetName of Object.keys(options.reserved)) {
      const reservedWordSetFn = options.reserved[reservedWordSetName]
      if (typeof reservedWordSetFn !== "function") {
        throw new Error(`Grammar reserved word sets must all be functions. '${reservedWordSetName}' is not.`);
      }

      const reservedTokens = reservedWordSetFn.call(ruleBuilder, ruleBuilder, baseGrammar.reserved[reservedWordSetName]);

View on GitHub (pinned to dff1fd868c)

Solutions

  1. Go to the rule named in the message and add/restore the `return` (or drop the braces to make it an expression arrow).
  2. Ensure every branch of a conditional rule returns a rule value.
  3. If you added debug logging, make sure the `return` is still the last statement.

Example fix

// before
source_file: $ => {
  console.log('building');
  repeat($.expression);
}

// after
source_file: $ => {
  console.log('building');
  return repeat($.expression);
}
Defensive patterns

Strategy: validation

Validate before calling

// smoke-test rules in isolation before running tree-sitter generate
const $ = new Proxy({}, { get: (_, k) => ({ type: 'SYMBOL', name: k }) });
for (const [name, fn] of Object.entries(grammarOptions.rules)) {
  const out = fn($, undefined);
  if (out === undefined) throw new Error(`rule '${name}' returned undefined — missing return?`);
}

Type guard

const returnsDefined = (fn, $, prev) => fn($, prev) !== undefined;

Try / catch

try {
  grammarOptions = grammar(grammarOptions);
} catch (e) {
  const m = /Rule '(.+)' returned undefined\./.exec(e.message);
  if (m) console.error(`rule '${m[1]}' has a code path without a return`);
  throw e;
}

Prevention

When it happens

Trigger: `source_file: $ => { repeat($.expression) }` (braces without return); `rules: { x: $ => { if (c) return $.a; } }` (missing else-return); refactoring `rule: $ => choice(...)` into a multi-line helper and forgetting the final return; a rule that returns the result of `console.log` or another void call.

Common situations: Prettier/auto-formatting turns a short expression arrow into a block body and the author forgets `return`; adding debug logging (`console.log(...)` as the last statement) silently swallows the return value; grammar refactors into builder functions with early exits.

Related errors


AI-assisted analysis of tree-sitter/tree-sitter@dff1fd868c (2026-08-16). Data as JSON: /api/errors/a45dd0c0e74cf6ad. Report an issue: GitHub.