{"record":{"id":"56f516dffb3438c2","repo":"remix-run/remix","slug":"missing-variable-name","errorCode":null,"errorMessage":"missing variable name","messagePattern":"missing variable name","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/route-pattern/src/lib/route-pattern/parse.ts","lineNumber":96,"sourceCode":"      let optional = optionalStack.pop()\n      if (optional === undefined) {\n        throw new ParseError('unmatched )', source, i)\n      }\n      if (optional.tokenIndex === tokens.length - 1) {\n        emptyOptionals.push(optional.sourceIndex)\n      }\n      optionals.set(optional.tokenIndex, tokens.length)\n      tokens.push({ type: char })\n      tokenIndices.push(i)\n      i += 1\n      continue\n    }\n\n    if (char === ':') {\n      i += 1\n      let name = IDENTIFIER_RE.exec(source.slice(i, span[1]))?.[0]\n      if (!name) {\n        throw new ParseError('missing variable name', source, i - 1)\n      }\n      tokens.push({ type: ':', name })\n      tokenIndices.push(i - 1)\n      i += name.length\n      continue\n    }\n\n    if (char === '*') {\n      i += 1\n      let name = IDENTIFIER_RE.exec(source.slice(i, span[1]))?.[0]\n      tokens.push({ type: '*', name: name ?? '*' })\n      tokenIndices.push(i - 1)\n      i += name?.length ?? 0\n      continue\n    }\n\n    if (char === separator) {\n      tokens.push({ type: 'separator' })","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/route-pattern/src/lib/route-pattern/parse.ts#L78-L114","documentation":"Pattern syntax uses `:name` for variable capture. A ':' was encountered but no valid identifier follows it before the end of the span, so there is no name to bind the capture to. parsePart throws 'missing variable name' pointing at the colon.","triggerScenarios":"Calling parseRoutePattern with a pattern like '/users/:' , '/:' , or '/files/:.json' where ':' is followed by a non-identifier character or nothing. Only IDENTIFIER_RE-compatible names are accepted.","commonSituations":"Truncating a pattern during refactor (':id' reduced to ':'); a colon intended literally in a path (e.g. time '12:30') left unescaped; templating bug leaving an empty placeholder '{{user}}' removed but ':' kept.","solutions":["Give the variable a name: '/users/:id'","Escape colons that are literal: '/schedule/\\:time' (see the escape handling in the same parser)","Check the error position — it points at the offending ':'"],"exampleFix":"// before\nlet pattern = parseRoutePattern('/users/:')\n\n// after\nlet pattern = parseRoutePattern('/users/:id')","handlingStrategy":"validation","validationCode":"let colonNames = [...source.matchAll(/:(\\w*)/g)].filter(m => !m[1])\nif (colonNames.length) throw new Error('Colon without a variable name')","typeGuard":"null","tryCatchPattern":"try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'missing variable name') { /* fix at e.position */ } }","preventionTips":["Escape literal colons with \\\\","Test every pattern constant parses"],"tags":["route-pattern","parse","syntax","params"],"backgroundTag":"invalid-route-pattern-syntax","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}