{"record":{"id":"a45dd0c0e74cf6ad","repo":"tree-sitter/tree-sitter","slug":"rule-rulename-returned-undefined","errorCode":null,"errorMessage":"Rule '${ruleName}' returned undefined.","messagePattern":"Rule '(.+?)' returned undefined\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/generate/src/dsl.js","lineNumber":331,"sourceCode":"\n  if (inherits && !/^[a-zA-Z_]\\w*$/.test(name)) {\n    throw new Error(\"Base grammar's 'name' property must not start with a digit and cannot contain non-word characters.\");\n  }\n\n  const rules = Object.assign({}, baseGrammar.rules);\n  if (options.rules) {\n    if (typeof options.rules !== \"object\") {\n      throw new Error(\"Grammar's 'rules' property must be an object.\");\n    }\n\n    for (const ruleName of Object.keys(options.rules)) {\n      const ruleFn = options.rules[ruleName];\n      if (typeof ruleFn !== \"function\") {\n        throw new Error(`Grammar rules must all be functions. '${ruleName}' rule is not.`);\n      }\n      const rule = ruleFn.call(ruleBuilder, ruleBuilder, baseGrammar.rules[ruleName]);\n      if (rule === undefined) {\n        throw new Error(`Rule '${ruleName}' returned undefined.`);\n      }\n      rules[ruleName] = normalize(rule);\n    }\n  }\n\n  let reserved = baseGrammar.reserved;\n  if (options.reserved) {\n    if (typeof options.reserved !== \"object\") {\n      throw new Error(\"Grammar's 'reserved' property must be an object.\");\n    }\n\n    for (const reservedWordSetName of Object.keys(options.reserved)) {\n      const reservedWordSetFn = options.reserved[reservedWordSetName]\n      if (typeof reservedWordSetFn !== \"function\") {\n        throw new Error(`Grammar reserved word sets must all be functions. '${reservedWordSetName}' is not.`);\n      }\n\n      const reservedTokens = reservedWordSetFn.call(ruleBuilder, ruleBuilder, baseGrammar.reserved[reservedWordSetName]);","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/tree-sitter/tree-sitter/blob/dff1fd868c750dbbae179fcd5c43ce987e4e0528/crates/generate/src/dsl.js#L313-L349","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before\nsource_file: $ => {\n  console.log('building');\n  repeat($.expression);\n}\n\n// after\nsource_file: $ => {\n  console.log('building');\n  return repeat($.expression);\n}","handlingStrategy":"validation","validationCode":"// smoke-test rules in isolation before running tree-sitter generate\nconst $ = new Proxy({}, { get: (_, k) => ({ type: 'SYMBOL', name: k }) });\nfor (const [name, fn] of Object.entries(grammarOptions.rules)) {\n  const out = fn($, undefined);\n  if (out === undefined) throw new Error(`rule '${name}' returned undefined — missing return?`);\n}","typeGuard":"const returnsDefined = (fn, $, prev) => fn($, prev) !== undefined;","tryCatchPattern":"try {\n  grammarOptions = grammar(grammarOptions);\n} catch (e) {\n  const m = /Rule '(.+)' returned undefined\\./.exec(e.message);\n  if (m) console.error(`rule '${m[1]}' has a code path without a return`);\n  throw e;\n}","preventionTips":["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."],"tags":["tree-sitter","grammar-config","rules","missing-return"],"backgroundTag":"missing-return-value","analyzedSha":"dff1fd868c750dbbae179fcd5c43ce987e4e0528","analyzedAt":"2026-08-16T22:01:49.632Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}