{"record":{"id":"eb0a6482055071d7","repo":"amruthpillai/reactive-resume","slug":"selector-cannot-end-with-a-combinator","errorCode":null,"errorMessage":"Selector cannot end with a combinator.","messagePattern":"Selector cannot end with a combinator\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/resume/src/stylesheet/selector.ts","lineNumber":263,"sourceCode":"\t\t\tconst name = astName(child) as Combinator;\n\t\t\tif (![\" \", \">\", \"+\", \"~\"].includes(name) || selectors.length === 0) {\n\t\t\t\tthrow new Error(\"Unsupported or misplaced combinator.\");\n\t\t\t}\n\t\t\tvalidateCompound(selectors);\n\t\t\tcompounds.push({ selectors });\n\t\t\tselectors = [];\n\t\t\tcombinators.push(name);\n\t\t\tif (combinators.length > SEMANTIC_CSS_LIMITS_V1.maxCombinatorsPerSelector) {\n\t\t\t\tthrow new Error(\"Selector has too many combinators.\");\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst selector = compileSimple(child, context);\n\t\tif (selector) selectors.push(selector);\n\t}\n\n\tif (selectors.length === 0 && compounds.length > 0) throw new Error(\"Selector cannot end with a combinator.\");\n\tvalidateCompound(selectors);\n\tcompounds.push({ selectors });\n\tconst specificity = SpecificityCalculator.calculateForAST(node).toArray();\n\treturn { compounds, combinators, specificity: [specificity[0], specificity[1], specificity[2]] };\n}\n\nfunction compileSelectorList(node: SelectorAst, context: CompileContext): readonly CompiledComplexSelector[] {\n\tif (node.type !== \"SelectorList\") throw new Error(\"Expected a SelectorList AST.\");\n\tconst selectors = childrenOf(node);\n\tif (selectors.length === 0 || selectors.length > SEMANTIC_CSS_LIMITS_V1.maxSelectorsPerRule) {\n\t\tthrow new Error(\"Selector list has an unsupported number of selectors.\");\n\t}\n\treturn selectors.map((selector) => compileComplex(selector, context));\n}\n\nexport function compileSelector(source: string | CssNode): CompileSelectorResult {\n\ttry {\n\t\tconst text = typeof source === \"string\" ? source : csstree.generate(source);","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/amruthpillai/reactive-resume/blob/3a5b12e2a40374a9571988701fcb75c5a1831c42/packages/resume/src/stylesheet/selector.ts#L245-L281","documentation":"Thrown by compileComplex after iterating a Selector AST when the loop produced at least one compound (so combinators were seen) but no trailing simple selectors remain. This means the parsed selector ends with a combinator token, e.g. 'div >' or 'a +'. A trailing combinator is syntactically invalid as a complete selector and would leave an empty final compound, so the compiler rejects it rather than emit an incomplete matcher. The error is only raised when compounds.length > 0 (at least one combinator existed) AND the trailing selectors buffer is empty.","triggerScenarios":"Author input that ends in a combinator with nothing after it: 'section >', 'item +', 'group ~', or 'a b >'. Also triggered by selectors with stray trailing whitespace after an explicit combinator where css-tree emits a Combinator node as the last child. Template/string interpolation bugs that concatenate a selector prefix with an empty suffix can produce this (e.g. ``${base} >`` when base is the only content).","commonSituations":"Hand-editing a semantic stylesheet and leaving a dangling combinator. Programmatic selector assembly where a conditional segment is omitted, leaving a trailing combinator. Copy-paste from a larger rule that lost its final compound. Whitespace-only content accidentally parsed after an explicit combinator.","solutions":["Inspect the offending selector and remove or complete the trailing combinator (e.g. change 'section >' to 'section > item').","If the selector is built by concatenation, guard each segment so a combinator is only appended when a following compound is guaranteed — build segments as compound+cominator pairs.","Validate selector strings with compileSelector() and surface the returned error field to the user at authoring time rather than at render time."],"exampleFix":"// before\nconst source = 'section >';\nconst { selector, error } = compileSelector(source);\n// selector === null, error === 'Selector cannot end with a combinator.'\n\n// after\nconst source = 'section > item';\nconst { selector, error } = compileSelector(source);\n// selector !== null, error === undefined","handlingStrategy":"validation","validationCode":"function endsWithCombinator(source: string): boolean {\n  return /[ >+~]\\s*$/.test(source.trim());\n}\nif (endsWithCombinator(source)) {\n  // strip the trailing combinator or append the missing compound\n}","typeGuard":"function isValidSelector(source: string): boolean {\n  return !endsWithCombinator(source);\n}","tryCatchPattern":"const { selector, error } = compileSelector(source);\nif (!selector && error === 'Selector cannot end with a combinator.') {\n  // prompt user to complete the selector\n}","preventionTips":["When building selectors by concatenation, never append a combinator without a guaranteed following compound.","Validate with compileSelector before persisting the stylesheet.","Trim trailing whitespace and re-check after every edit."],"tags":["css","semantic-stylesheet","selector","validation"],"backgroundTag":null,"analyzedSha":"3a5b12e2a40374a9571988701fcb75c5a1831c42","analyzedAt":"2026-08-12T22:31:22.666Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}