{"record":{"id":"993af1138209b409","repo":"remix-run/remix","slug":"adjacent-wildcards","errorCode":null,"errorMessage":"adjacent wildcards","messagePattern":"adjacent wildcards","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/route-pattern/src/lib/route-pattern/parse.ts","lineNumber":162,"sourceCode":"  return { tokens, optionals, type: options.type }\n}\n\nfunction validateTokenGrammar(\n  source: string,\n  tokens: ReadonlyArray<PartPatternToken>,\n  optionals: ReadonlyMap<number, number>,\n  tokenIndices: ReadonlyArray<number>,\n): void {\n  for (let [index, token] of tokens.entries()) {\n    if (token.type !== ':' && token.type !== '*') continue\n\n    for (let next of nextConsumingTokenIndices(tokens, optionals, index + 1)) {\n      if (next === tokens.length) continue\n      let nextToken = tokens[next]\n\n      if (token.type === '*') {\n        if (nextToken.type === '*') {\n          throw new ParseError('adjacent wildcards', source, tokenIndices[next])\n        }\n        continue\n      }\n\n      if (\n        nextToken.type === 'separator' ||\n        nextToken.type === '*' ||\n        (nextToken.type === 'text' && nextToken.text.startsWith('.'))\n      ) {\n        continue\n      }\n      throw new ParseError('invalid param delimiter', source, tokenIndices[next])\n    }\n  }\n}\n\nfunction nextConsumingTokenIndices(\n  tokens: ReadonlyArray<PartPatternToken>,","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/route-pattern/src/lib/route-pattern/parse.ts#L144-L180","documentation":"During grammar validation after tokenizing, two wildcard (`*`) tokens were found adjacent to each other with no separator between them (e.g. '/a/**/b' as two splats back-to-back). Adjacent wildcards are ambiguous to match and serialize, so a ParseError 'adjacent wildcards' is thrown at the second wildcard's index.","triggerScenarios":"Calling parseRoutePattern with a pattern containing two splat tokens next to each other or separated only by optionals that can collapse, such as '/files/*/*/download' or '/a/*/b(*)/c' where the optional resolves to adjacent wildcards.","commonSituations":"Copying glob syntax (`**`) from file matchers into route patterns; concatenating two catch-all fragments; refactoring nested routes into one pattern and stacking splats.","solutions":["Use a single wildcard: '/files/*/download' or '/files/*'","If you need multi-segment matching, one splat already spans separators — use a named splat '*rest'","Insert a literal or named param between the wildcards if both are genuinely needed"],"exampleFix":"// before\nlet pattern = parseRoutePattern('/files/*/*/download')\n\n// after\nlet pattern = parseRoutePattern('/files/*rest/download')","handlingStrategy":"validation","validationCode":"if (/\\*(\\([^)]*\\))?\\*/.test(source.replace(/\\\\./g, ''))) throw new Error('Adjacent wildcards')","typeGuard":"null","tryCatchPattern":"try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'adjacent wildcards') { /* collapse to one splat */ } }","preventionTips":["One splat per region; use named splats","Don't copy glob '**' syntax"],"tags":["route-pattern","parse","wildcard","syntax"],"backgroundTag":"invalid-route-pattern-syntax","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}