{"record":{"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/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/utils/tags.ts#L166-L202","documentation":"Thrown by TokenStream.expect when a specific token type was expected (the only current call site expects RPAREN after a grouped sub-expression) but a different token was found, and it is not the EOF-after-expecting-paren case covered by error 343. The message names both the expected and actual tokens to pinpoint the mismatch.","triggerScenarios":"A `--tags` expression where a grouping opens with `(` but the next significant token is not a valid close, e.g. `vitest --tags '(foo bar)'` (two tags inside parens with no operator — expect RPAREN after 'foo', got TAG 'bar'), or `vitest --tags '(foo &&)'` (expect RPAREN, got an unexpected token).","commonSituations":"Putting multiple tags inside one pair of parentheses without an operator; trailing operators inside a group; stray punctuation inside a group.","solutions":["Inside parentheses put exactly one sub-expression (tag, !tag, or a binary op chain): `--tags '(foo && bar)'`.","Remove the unexpected token identified by 'but got' in the message.","Re-read the grammar: a group is `( <or-expression> )`, so combine tags inside it with && / || / ! only."],"exampleFix":"# before\nvitest --tags '(smoke auth)'\n\n# after\nvitest --tags '(smoke && auth)'","handlingStrategy":"validation","validationCode":"// Inside a group, allow only: TAG, !TAG, ( ... ), joined by && / ||\nfunction validateGroup(group: string): boolean {\n  const inner = group.replace(/^[^(]*\\(/, '').replace(/\\)[^)]*$/, '')\n  return /^!?[\\w*-]+(?:\\s*(?:&&|\\|\\|)\\s*!?[\\w*-]+)*$/.test(inner.trim())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat each parenthesis pair as containing exactly one boolean expression over tags.","Do not put multiple bare tags inside one pair of parentheses.","When a filter grows complex, build and unit-test it in a helper instead of inlining in CLI."],"tags":["cli","tags","parser","validation"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}