{"record":{"id":"59a5a985d7a7ab7b","repo":"tree-sitter/tree-sitter","slug":"grammar-s-word-property-must-be-a-named-rule","errorCode":null,"errorMessage":"Grammar's 'word' property must be a named rule.","messagePattern":"Grammar's 'word' property must be a named rule\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/generate/src/dsl.js","lineNumber":379,"sourceCode":"    if (typeof options.extras !== \"function\") {\n      throw new Error(\"Grammar's 'extras' property must be a function.\");\n    }\n\n    extras = options.extras\n      .call(ruleBuilder, ruleBuilder, baseGrammar.extras)\n\n    if (!Array.isArray(extras)) {\n      throw new Error(\"Grammar's 'extras' function must return an array.\")\n    }\n\n    extras = extras.map(normalize);\n  }\n\n  let word = baseGrammar.word;\n  if (options.word) {\n    word = options.word.call(ruleBuilder, ruleBuilder).name;\n    if (typeof word != 'string') {\n      throw new Error(\"Grammar's 'word' property must be a named rule.\");\n    }\n\n    if (word === 'ReferenceError') {\n      throw new Error(\"Grammar's 'word' property must be a valid rule name.\");\n    }\n  }\n\n  let conflicts = baseGrammar.conflicts;\n  if (options.conflicts) {\n    if (typeof options.conflicts !== \"function\") {\n      throw new Error(\"Grammar's 'conflicts' property must be a function.\");\n    }\n\n    const baseConflictRules = baseGrammar.conflicts.map(conflict => conflict.map(sym));\n    const conflictRules = options.conflicts.call(ruleBuilder, ruleBuilder, baseConflictRules);\n\n    if (!Array.isArray(conflictRules)) {\n      throw new Error(\"Grammar's conflicts must be an array of arrays of rules.\");","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/tree-sitter/tree-sitter/blob/dff1fd868c750dbbae179fcd5c43ce987e4e0528/crates/generate/src/dsl.js#L361-L397","documentation":"Thrown by `grammar({...})` when the `word` option's callback result has no string `name` property. `word` designates the rule used for keyword extraction, and it must be a reference to a named rule — the DSL takes `.name` of the returned value directly, without normalizing it, so only a symbol like `$.identifier` (whose object has `name`) satisfies the check. Inline tokens, patterns, strings, or composite nodes have no `name` and throw. (If you return a rule name that doesn't exist, the proxy stub's `name` is `'ReferenceError'`, which passes this check and is caught by the follow-up check 'must be a valid rule name'.)","triggerScenarios":"`word: $ => token(/[a-zA-Z_]\\w*/)` (inline token object has no `.name`); `word: $ => 'identifier'` (string primitive); `word: $ => seq(...)` or `choice(...)` (composite node); `word: $ => $.undefined_rule` (throws the follow-up 'valid rule name' error instead, at line 382).","commonSituations":"Authors inline the identifier token directly in the `word` slot instead of defining it as a named rule first; refactoring an inline identifier into `word` without moving the token into its own rule; grammars where the word rule is built by a helper returning a node rather than a symbol.","solutions":["Define the word token as a named rule, then reference the symbol: `identifier: $ => /[a-zA-Z_]\\w*/` plus `word: $ => $.identifier`.","Keep `word` pointing at exactly one named symbol — no `token()`, `seq()`, or literals in the return slot.","If you got 'must be a valid rule name' instead, the referenced rule doesn't exist — check spelling/placement in `rules`."],"exampleFix":"// before\nword: $ => token(/[a-zA-Z_]\\w*/)\n\n// after\nrules: {\n  identifier: $ => /[a-zA-Z_]\\w*/,\n  ...\n},\nword: $ => $.identifier","handlingStrategy":"validation","validationCode":"function validateWordOption(options) {\n  if (options.word) {\n    if (typeof options.word !== 'function') throw new Error('word must be a function');\n    const $ = new Proxy({}, { get: (_, k) => ({ type: 'SYMBOL', name: k }) });\n    const out = options.word($, undefined);\n    if (typeof out?.name !== 'string' || out.name === 'ReferenceError') {\n      throw new Error('word must return a named rule symbol, e.g. $ => $.identifier');\n    }\n  }\n}","typeGuard":"const isNamedSymbol = (v) =>\n  typeof v === 'object' && v !== null && typeof v.name === 'string' && v.name !== 'ReferenceError';","tryCatchPattern":null,"preventionTips":["Define the word token as its own named rule (e.g. `identifier`) and set `word: $ => $.identifier`.","Never inline `token(...)` or regexes in the `word` slot — the DSL reads `.name` directly.","Check the follow-up 'valid rule name' error too: it means the referenced rule doesn't exist in `rules`."],"tags":["tree-sitter","grammar-config","word","named-rule"],"backgroundTag":"invalid-word-rule","analyzedSha":"dff1fd868c750dbbae179fcd5c43ce987e4e0528","analyzedAt":"2026-08-16T22:01:49.632Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}