{"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/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/utils/tags.ts#L174-L210","documentation":"Thrown by TokenStream.unexpectedToken when the parser needs another primary operand but the stream is already at EOF. This occurs when an operator (&&, ||, or !) is followed by nothing - the expression ends where a tag or group was expected.","triggerScenarios":"A trailing operator: `--tag 'unit &&'`, `--tag 'unit ||'`, or a leading/trailing `!` with nothing after, e.g. `--tag '!'`.","commonSituations":"Accidentally truncating a filter expression. Editing a filter and deleting the last tag. Using `!` alone expecting negation of everything.","solutions":["Add the missing right-hand operand after the operator: `unit && smoke`.","Remove the trailing operator if you did not mean to combine anything.","Ensure every `!` is immediately followed by a tag or group."],"exampleFix":"// before\nvitest --tag 'unit &&'\n\n// after\nvitest --tag 'unit && smoke'","handlingStrategy":"validation","validationCode":"// Reject expressions ending with an operator\nfunction endsWithOperator(expr: string): boolean {\n  return /(?:&&|\\|\\||!|\\band\\b|\\bor\\b|\\bnot\\b)\\s*$/i.test(expr.trim())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure every operator has a right-hand operand.","Do not end an expression with &&, ||, or !.","Build filter expressions programmatically rather than by string concatenation."],"tags":["tags","filter-expression","parser"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}