amruthpillai/reactive-resume · error · Error

Attribute selector matcher and value must be used together.

Error message

Attribute selector matcher and value must be used together.

What it means

Thrown by compileSimple in the AttributeSelector branch when a matcher and a value are not both present or both absent (the XOR of 'matcher is null/undefined' and 'value is null' is true). A bare [attr=val] needs both; a presence [attr] needs neither.

Source

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

		case "TypeSelector": {
			const name = astName(node);
			if (name === "*") return { type: "universal" };
			if (!knownKinds.has(name)) throw new Error(`Unknown semantic element ${name}.`);
			return { type: "type", name: name as SemanticNode["kind"] };
		}
		case "IdSelector":
			return { type: "id", value: astName(node) };
		case "AttributeSelector": {
			const name = astName(node);
			if (!knownAttributes.has(name)) throw new Error(`Unknown semantic attribute ${name}.`);
			if (node.flags) throw new Error("Attribute selector flags are not supported.");
			const matcher = node.matcher as AttributeMatcher | null | undefined;
			if (matcher !== null && matcher !== undefined && !["=", "~=", "|=", "^=", "$=", "*="].includes(matcher)) {
				throw new Error(`Unsupported attribute matcher ${matcher}.`);
			}
			const value = attributeValue(node);
			if ((matcher === null || matcher === undefined) !== (value === null)) {
				throw new Error("Attribute selector matcher and value must be used together.");
			}
			return { type: "attribute", name, matcher: matcher ?? null, value };
		}
		case "PseudoClassSelector": {
			const name = astName(node);
			if (["root", "first-child", "last-child", "only-child"].includes(name)) {
				if (node.children !== null && node.children !== undefined)
					throw new Error(`:${name} does not accept arguments.`);
				return { type: "pseudo", name: name as "root" | "first-child" | "last-child" | "only-child" };
			}
			if (["is", "where", "not"].includes(name)) {
				if (context.depth >= SEMANTIC_CSS_LIMITS_V1.maxFunctionDepth) {
					throw new Error("Selector function nesting is too deep.");
				}
				const nested = childrenOf(node);
				if (nested.length !== 1 || nested[0]?.type !== "SelectorList") {
					throw new Error(`:${name} requires a selector list.`);
				}

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. For a value comparison, include both: [type='experience'].
  2. For presence only, include neither: [type].
  3. Validate the (matcher, value) pairing before interpolating into a selector string.

Example fix

// before
// AST: { name:'type', matcher:'=', value:null }
// after
[type='experience']
Defensive patterns

Strategy: validation

Validate before calling

const matcherAndValuePaired = (matcher, value) =>
  (matcher === null || matcher === undefined) === (value === null);
// verify before constructing an AttributeSelector node

Type guard

const isRejected = (r) => r.selector === null && typeof r.error === 'string';

Try / catch

const result = compileSelector(selectorOrAst);
if (result.selector === null && result.error === 'Attribute selector matcher and value must be used together.') {
  addMissingValueOrMatcher();
}

Prevention

When it happens

Trigger: A programmatically built AST with a matcher but no value, or a value but no matcher; malformed selector fragments produced by string concatenation.

Common situations: Templating selector strings and forgetting the value; constructing CssNode objects manually; partial regex substitutions on selectors.

Related errors


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