{"record":{"id":"683b68d1e649511f","repo":"vitest-dev/vitest","slug":"invalid-tags-expression-unexpected-formattoken","errorCode":null,"errorMessage":"Invalid tags expression: unexpected \"${formatToken(stream.peek())}\" in \"${expr}\"","messagePattern":"Invalid tags expression: unexpected \"(.+?)\" in \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/utils/tags.ts","lineNumber":60,"sourceCode":"    .map(t => `- ${t.name}${t.description ? `: ${t.description}` : ''}`)\n    .join('\\n')}`)\n}\n\nexport function createTagsFilter(tagsExpr: string[], availableTags: TestTagDefinition[]): (testTags: string[]) => boolean {\n  const matchers = tagsExpr.map(expr => parseTagsExpression(expr, availableTags))\n  return (testTags: string[]) => {\n    return matchers.every(matcher => matcher(testTags))\n  }\n}\n\ntype TagMatcher = (tags: string[]) => boolean\n\nfunction parseTagsExpression(expr: string, availableTags: TestTagDefinition[]): TagMatcher {\n  const tokens = tokenize(expr)\n  const stream = new TokenStream(tokens, expr)\n  const ast = parseOrExpression(stream, availableTags)\n  if (stream.peek().type !== 'EOF') {\n    throw new Error(`Invalid tags expression: unexpected \"${formatToken(stream.peek())}\" in \"${expr}\"`)\n  }\n  return (tags: string[]) => evaluateNode(ast, tags)\n}\n\nfunction formatToken(token: Token): string {\n  switch (token.type) {\n    case 'TAG': return token.value\n    default: return formatTokenType(token.type)\n  }\n}\n\ntype Token\n  = | { type: 'TAG'; value: string }\n    | { type: 'AND' }\n    | { type: 'OR' }\n    | { type: 'NOT' }\n    | { type: 'LPAREN' }\n    | { type: 'RPAREN' }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/utils/tags.ts#L42-L78","documentation":"Thrown by parseTagsExpression after a top-level OR-expression parse completes but the token stream still has a non-EOF token. This indicates the expression parsed successfully up to a point and then an unexpected token followed that cannot extend a valid expression (e.g. two consecutive tags with no operator). It governs the `--tags` CLI filter grammar, which supports tag names plus `&&`/`||`/`!`/parentheses and the words and/or/not.","triggerScenarios":"Running `vitest --tags 'foo bar'` (two tags with no operator between them), `vitest --tags 'foo )'` (stray closing paren after a tag), or any expression where a second complete sub-expression follows the first without `&&`/`||`/`and`/`or`.","commonSituations":"Users assume multiple tags are space-separated (AND) like some other tools; misplaced parentheses; pasting a filter expression from documentation that used a different syntax.","solutions":["Join multiple required tags with `&&` or the word `and`: `--tags 'foo && bar'`.","Join alternatives with `||` or `or`: `--tags 'foo || bar'`.","Remove the stray token the error points at (the unexpected token is quoted in the message).","Wrap sub-expressions in parentheses only as complete units: `--tags '(foo || bar) && baz'`."],"exampleFix":"# before\nvitest --tags 'smoke auth'\n\n# after\nvitest --tags 'smoke && auth'","handlingStrategy":"validation","validationCode":"// Validate a --tags expression before passing to vitest\nimport { execFileSync } from 'node:child_process'\n// Cheap pre-check: every pair of tags must be separated by an operator\nfunction looksBalanced(expr: string): boolean {\n  const ops = expr.split(/\\s*(?:&&|\\|\\||\\band\\b|\\bor\\b)\\s*/i)\n  return ops.every(p => p.trim().length > 0)\n}","typeGuard":null,"tryCatchPattern":"// In a wrapper script that invokes vitest --tags\ntry {\n  runVitest(['--tags', expr])\n} catch (e) {\n  if (String(e.message).startsWith('Invalid tags expression')) {\n    console.error('Tags filter syntax error. Use tag && tag, tag || tag, !tag, ( ... ).')\n    process.exit(2)\n  }\n  throw e\n}","preventionTips":["Reuse a small, documented set of tag-filter snippets rather than hand-typing each time.","When joining tags programmatically, always insert an operator: parts.join(' && ').","Keep a cheat-sheet of the grammar (tags, && / || / !, parentheses, and/or/not words) near your run scripts."],"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"}