{"record":{"id":"9dc96fa657573faf","repo":"Stirling-Tools/Stirling-PDF","slug":"unexpected-trailing-input","errorCode":null,"errorMessage":"Unexpected trailing input","messagePattern":"Unexpected trailing input","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"frontend/editor/src/core/utils/bulkselection/parseSelection.ts","lineNumber":130,"sourceCode":"}\n\nclass ExpressionParser {\n  private readonly src: string;\n  private readonly max: number;\n  private idx: number = 0;\n\n  constructor(source: string, maxPages: number) {\n    this.src = source;\n    this.max = maxPages;\n  }\n\n  parse(): Set<number> {\n    this.skipWs();\n    const set = this.parseDisjunction();\n    this.skipWs();\n    // If there are leftover non-space characters, treat as error\n    if (this.idx < this.src.length) {\n      throw new Error(\"Unexpected trailing input\");\n    }\n    return set;\n  }\n\n  private parseDisjunction(): Set<number> {\n    let left = this.parseConjunction();\n    while (true) {\n      this.skipWs();\n      const op = this.peekWordOrSymbol();\n      if (!op) break;\n      if (op.type === \"symbol\" && (op.value === \",\" || op.value === \"|\")) {\n        this.consume(op.length);\n        const right = this.parseConjunction();\n        left = union(left, right);\n        continue;\n      }\n      if (op.type === \"word\" && op.value === \"or\") {\n        this.consume(op.length);","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/utils/bulkselection/parseSelection.ts#L112-L148","documentation":"Thrown by ExpressionParser.parse when, after successfully parsing a top-level disjunction and skipping whitespace, the parser index has not reached the end of the string — meaning there are leftover characters the grammar cannot consume. Note: the public parseSelection() wraps this in try/catch and silently falls back to CSV, so this error only propagates to callers via parseSelectionWithDiagnostics with the strict option enabled.","triggerScenarios":"Input like '1-5 xyz' (parser consumes '1-5', then 'xyz' is leftover), '1,2;3' (semicolons not in grammar), or '1 2' (two numbers with no operator between them). Any input where the valid prefix parses but trailing tokens remain that are not operators or whitespace.","commonSituations":"User types a page selection with a stray character, an unsupported separator (e.g. ';'), or pastes a selection from another tool that uses different syntax. Strict mode is enabled in a bulk-selection input, so the fallback is bypassed and the raw error surfaces.","solutions":["Check the input immediately after the last valid token for stray characters or unsupported separators.","Replace unsupported separators with the grammar's operators: ',' or '|' for OR, '&' or 'and' for conjunction.","If using parseSelectionWithDiagnostics, handle the returned warning field instead of enabling strict mode unless you want hard failures.","Trim and sanitize the input before parsing."],"exampleFix":"// before\nparseSelectionWithDiagnostics('1-5 ; 7', max, { strict: true })\n// after\nparseSelectionWithDiagnostics('1-5, 7', max, { strict: true })","handlingStrategy":"validation","validationCode":"import { parseSelectionWithDiagnostics } from '@app/utils/bulkselection/parseSelection';\n\n// Use non-strict mode to get a warning instead of a throw:\nconst { pages, warning } = parseSelectionWithDiagnostics(input, maxPages);\nif (warning) showUser(warning);\n// pages still contains the CSV-fallback result.","typeGuard":"function looksLikeCompleteExpression(input: string): boolean {\n  // Reject inputs with characters outside the supported grammar\n  return /^[-(),|&!\\s\\dA-Za-z+*n]+$/.test(input);\n}","tryCatchPattern":"try {\n  const { pages } = parseSelectionWithDiagnostics(input, maxPages, { strict: true });\n} catch (e) {\n  if (e instanceof Error && e.message === 'Unexpected trailing input') {\n    showUser('Check for stray characters after your selection.');\n  } else { throw e; }\n}","preventionTips":["Prefer parseSelectionWithDiagnostics without strict mode to get warnings instead of exceptions.","Sanitize input to strip unsupported separator characters before parsing.","Provide a live preview of parsed pages so users see issues before strict validation.","Document the supported grammar near the input field."],"tags":["bulk-selection","parser","validation","user-input"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}