amruthpillai/reactive-resume · error · Error

Selector cannot end with a combinator.

Error message

Selector cannot end with a combinator.

What it means

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.

Source

Thrown at packages/resume/src/stylesheet/selector.ts:263

			const name = astName(child) as Combinator;
			if (![" ", ">", "+", "~"].includes(name) || selectors.length === 0) {
				throw new Error("Unsupported or misplaced combinator.");
			}
			validateCompound(selectors);
			compounds.push({ selectors });
			selectors = [];
			combinators.push(name);
			if (combinators.length > SEMANTIC_CSS_LIMITS_V1.maxCombinatorsPerSelector) {
				throw new Error("Selector has too many combinators.");
			}
			continue;
		}

		const selector = compileSimple(child, context);
		if (selector) selectors.push(selector);
	}

	if (selectors.length === 0 && compounds.length > 0) throw new Error("Selector cannot end with a combinator.");
	validateCompound(selectors);
	compounds.push({ selectors });
	const specificity = SpecificityCalculator.calculateForAST(node).toArray();
	return { compounds, combinators, specificity: [specificity[0], specificity[1], specificity[2]] };
}

function compileSelectorList(node: SelectorAst, context: CompileContext): readonly CompiledComplexSelector[] {
	if (node.type !== "SelectorList") throw new Error("Expected a SelectorList AST.");
	const selectors = childrenOf(node);
	if (selectors.length === 0 || selectors.length > SEMANTIC_CSS_LIMITS_V1.maxSelectorsPerRule) {
		throw new Error("Selector list has an unsupported number of selectors.");
	}
	return selectors.map((selector) => compileComplex(selector, context));
}

export function compileSelector(source: string | CssNode): CompileSelectorResult {
	try {
		const text = typeof source === "string" ? source : csstree.generate(source);

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Inspect the offending selector and remove or complete the trailing combinator (e.g. change 'section >' to 'section > item').
  2. 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.
  3. Validate selector strings with compileSelector() and surface the returned error field to the user at authoring time rather than at render time.

Example fix

// before
const source = 'section >';
const { selector, error } = compileSelector(source);
// selector === null, error === 'Selector cannot end with a combinator.'

// after
const source = 'section > item';
const { selector, error } = compileSelector(source);
// selector !== null, error === undefined
Defensive patterns

Strategy: validation

Validate before calling

function endsWithCombinator(source: string): boolean {
  return /[ >+~]\s*$/.test(source.trim());
}
if (endsWithCombinator(source)) {
  // strip the trailing combinator or append the missing compound
}

Type guard

function isValidSelector(source: string): boolean {
  return !endsWithCombinator(source);
}

Try / catch

const { selector, error } = compileSelector(source);
if (!selector && error === 'Selector cannot end with a combinator.') {
  // prompt user to complete the selector
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/eb0a6482055071d7. Report an issue: GitHub.