{"record":{"id":"89b61a7f14b3ba53","repo":"vitest-dev/vitest","slug":"invalid-tags-expression-unexpected-end-of-express","errorCode":null,"errorMessage":"Invalid tags expression: unexpected end of expression in \"${this.expr}\"","messagePattern":"Invalid tags expression: unexpected end of expression in \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/utils/tags.ts","lineNumber":192,"sourceCode":"  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'\n    case 'NOT': return 'not'\n    case 'LPAREN': return '('\n    case 'RPAREN': return ')'\n    case 'EOF': return 'end of expression'\n  }\n}\n\nfunction parseOrExpression(stream: TokenStream, availableTags: TestTagDefinition[]): ASTNode {","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/utils/tags.ts#L174-L210","documentation":"Thrown by TokenStream.unexpectedToken when the parser is at a position requiring a primary expression (a tag or a `(`) but the current token is EOF — i.e. the expression ended prematurely. This happens when a binary or unary operator has nothing after it.","triggerScenarios":"A `--tags` expression ending with a binary operator (`vitest --tags 'foo &&'`, `vitest --tags 'foo ||'`), a `not`/`!` with no following tag (`vitest --tags '!'` or `--tags 'not'`), or an empty expression after an operator inside parens (`--tags '(foo && )'` would surface via 344, but a bare trailing `&&` hits 345).","commonSituations":"Trailing operator typed by accident; the tag name after an operator was deleted; building the expression programmatically and leaving a dangling operator.","solutions":["Add the missing operand after the trailing operator: `--tags 'foo && bar'`.","Remove the trailing operator if no second operand is intended: `--tags 'foo'`.","Ensure every `!`/`not` is followed by a tag or parenthesized sub-expression."],"exampleFix":"# before\nvitest --tags 'smoke &&'\n\n# after\nvitest --tags 'smoke && critical'","handlingStrategy":"validation","validationCode":"function noTrailingOperator(expr: string): boolean {\n  return !/(?:&&|\\|\\||!|\\bnot\\b|\\band\\b|\\bor\\b)\\s*$/i.test(expr.trim())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When constructing filters in code, filter out empty fragments before joining.","Visually confirm every operator has a right-hand operand.","Avoid trailing whitespace after operators when editing by hand."],"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"}