{"id":"cc39c0d2aee95435","repo":"vitest-dev/vitest","slug":"invalid-tags-expression-expected-formattokenty","errorCode":null,"errorMessage":"Invalid tags expression: expected \"${formatTokenType(type)}\" but got \"${formatToken(token)}\" in \"${this.expr}\"","messagePattern":"Invalid tags expression: expected \"(.+?)\" but got \"(.+?)\" in \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/utils/tags.ts","lineNumber":184,"sourceCode":"class TokenStream {\n  private pos = 0\n  constructor(private tokens: Token[], public expr: string) {}\n\n  peek(): Token {\n    return this.tokens[this.pos]\n  }\n\n  next(): Token {\n    return this.tokens[this.pos++]\n  }\n\n  expect(type: Token['type']): Token {\n    const token = this.next()\n    if (token.type !== type) {\n      if (type === 'RPAREN' && token.type === 'EOF') {\n        throw new Error(`Invalid tags expression: missing closing \")\" in \"${this.expr}\"`)\n      }\n      throw new Error(`Invalid tags expression: expected \"${formatTokenType(type)}\" but got \"${formatToken(token)}\" in \"${this.expr}\"`)\n    }\n    return token\n  }\n\n  unexpectedToken(): never {\n    const token = this.peek()\n    if (token.type === 'EOF') {\n      throw new Error(`Invalid tags expression: unexpected end of expression in \"${this.expr}\"`)\n    }\n    throw new Error(`Invalid tags expression: unexpected \"${formatToken(token)}\" in \"${this.expr}\"`)\n  }\n}\n\nfunction formatTokenType(type: Token['type']): string {\n  switch (type) {\n    case 'TAG': return 'tag'\n    case 'AND': return 'and'\n    case 'OR': return 'or'","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/utils/tags.ts#L166-L202","documentation":"Thrown by TokenStream.expect when a specific token type was required but a different one appeared. Currently the only path that reaches a non-RPAREN expect is expect('RPAREN') hitting a non-EOF token (line 184 fires for that case), so in practice this message means: expected a ')' but got some other token (a tag, '&&', etc.) before end of input.","triggerScenarios":"A tag filter like `--tag '(unit smoke)'` where the inner expression `unit smoke` has two tags with no operator; after parsing 'unit' the and/or loops stop at the bare 'smoke' token, then expect(RPAREN) finds the 'smoke' TAG token instead of ')'.","commonSituations":"Putting two tags inside parentheses without an operator: `(a b)`. Adding an operator before the closing paren like `(a && )`.","solutions":["Insert the missing operator between tokens inside the group: `(a && b)`.","Remove the unexpected token so the inner expression is a single complete or/and/not tree.","Validate the expression is a single balanced boolean tree of tags and operators."],"exampleFix":"// before\nvitest --tag '(unit smoke)'\n\n// after\nvitest --tag '(unit || smoke)'","handlingStrategy":"validation","validationCode":"// Reject expressions where a TAG is immediately followed by another TAG or by ')'\nfunction hasAdjacentOperands(expr: string): boolean {\n  return /(\\b\\w+\\b)\\s+(\\b\\w+\\b)/.test(expr) // crude: flag for manual review\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Place an operator between every pair of tags inside a group.","Do not put operators directly before a closing paren.","Lint filter expressions in a preflight step."],"tags":["tags","filter-expression","parser"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}