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
- Go to the rule named in the message and add/restore the `return` (or drop the braces to make it an expression arrow).
- Ensure every branch of a conditional rule returns a rule value.
- 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
- Prefer expression-bodied arrows (`$ => seq(...)`) so a missing return is impossible.
- After adding console.log or branches inside a rule, re-check that the last statement is the returned rule.
- Smoke-test rule functions with a fake `$` proxy in a unit test to catch undefined returns before generate.
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
- Grammar's 'rules' property must be an object.
- Grammar rules must all be functions. '${ruleName}' rule is n
- Grammar's 'externals' property must be a function.
- Grammar's 'externals' property must return an array of rules
- Grammar's 'name' property must be a string.
AI-assisted analysis of tree-sitter/tree-sitter@dff1fd868c (2026-08-16).
Data as JSON: /api/errors/a45dd0c0e74cf6ad.
Report an issue: GitHub.