Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@types/chai": "^4.3.20",
"@types/mocha": "^10.0.10",
"@types/node": "22",
"@webref/css": "~6.16.1",
"@webref/css": "^6.18.0",
"chai": "^4.3.10",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/css-value-parser/src/test/css-value-syntax.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect } from 'chai';
//TODO: fixme
import {
bar,
booleanExpr,
dataType,
doubleAmpersand,
doubleBar,
Expand Down Expand Up @@ -249,4 +250,15 @@ describe('value-syntax-parser', () => {
);
});
});

describe('boolean-expr', () => {
// https://drafts.csswg.org/css-values-5/#boolean
it('should parse with inner test nodes', () => {
const x = parseValueSyntax(`<boolean-expr[<if-test>]>`);
expect(x).to.eql(booleanExpr([dataType('if-test')]));
});
it('should fail for missing nodes', () => {
expect(() => parseValueSyntax(`<boolean-expr>`)).to.throw('missing boolean expression');
});
});
});
60 changes: 48 additions & 12 deletions packages/css-value-parser/src/value-syntax-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ interface PropertyNode {
multipliers?: Multipliers;
}

interface BooleanExpr {
type: 'boolean-expr';
test: ValueSyntaxAstNode[];
}

interface LiteralNode {
type: 'literal';
name: string;
Expand Down Expand Up @@ -125,7 +130,7 @@ interface GroupNode extends CombinatorGroup {
type Combinators = GroupNode | JuxtaposingNode | DoubleAmpersandNode | DoubleBarNode | BarNode;

type Components = DataTypeNode | PropertyNode;
export type ValueSyntaxAstNode = Components | KeywordNode | LiteralNode | Combinators;
export type ValueSyntaxAstNode = Components | KeywordNode | LiteralNode | Combinators | BooleanExpr;

export function literal(name: string, enclosed = false, multipliers?: Multipliers): LiteralNode {
return { type: 'literal', name, enclosed, multipliers };
Expand All @@ -143,6 +148,10 @@ export function dataType(name: string, range?: Range, multipliers?: Multipliers)
return { type: 'data-type', name, range, multipliers };
}

export function booleanExpr(test: ValueSyntaxAstNode[]): BooleanExpr {
return { type: 'boolean-expr', test };
}

export function group(nodes: ValueSyntaxAstNode[], multipliers?: Multipliers): GroupNode {
return { type: 'group', nodes, multipliers };
}
Expand Down Expand Up @@ -198,21 +207,28 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {

const type = getLiteralValueType(name);
let range: Range | undefined;
let booleanTest: ValueSyntaxAstNode[] | undefined;
if (type === 'invalid') {
throw new Error('missing data type name');
} else {
const t = s.eat('space').next();
if (t.type === '>') {
closed = true;
} else if (t.type === '[') {
const min = s.eat('space').take('text');
const sep = s.eat('space').take(',');
const max = s.eat('space').take('text');
const end = s.eat('space').take(']');
if (min && sep && max && end) {
range = [parseNumber(min.value), parseNumber(max.value)];
if (name === 'boolean-expr') {
// https://drafts.csswg.org/css-values-5/#boolean
const _test = s.run(handleToken, { ast: [] }, _source);
booleanTest = _test.ast;
} else {
throw new Error('Invalid range');
const min = s.eat('space').take('text');
const sep = s.eat('space').take(',');
const max = s.eat('space').take('text');
const end = s.eat('space').take(']');
if (min && sep && max && end) {
range = [parseNumber(min.value), parseNumber(max.value)];
} else {
throw new Error('Invalid range');
}
}
const t = s.eat('space').take('>');
if (t) {
Expand All @@ -223,7 +239,12 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
if (!closed) {
throw new Error('missing ">"');
}
if (type === 'quoted') {
if (name === 'boolean-expr') {
if (!booleanTest) {
throw new Error('missing boolean expression');
}
ast.push(booleanExpr(booleanTest));
} else if (type === 'quoted') {
ast.push(property(name.slice(1, -1), range));
} else {
ast.push(dataType(name, range));
Expand Down Expand Up @@ -275,7 +296,12 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
s.eat('space');
} else if (isRangeMultiplier(token)) {
const node = lastParsedNode(ast);
if (!node || node.type === 'juxtaposing' || isLowLevelGroup(node)) {
if (
!node ||
node.type === 'juxtaposing' ||
node.type === 'boolean-expr' ||
isLowLevelGroup(node)
) {
throw new Error('unexpected modifier');
}
node.multipliers ??= {};
Expand All @@ -289,7 +315,12 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
} else {
const node = lastParsedNode(ast);

if (!node || isLowLevelGroup(node) || node.type === 'juxtaposing') {
if (
!node ||
isLowLevelGroup(node) ||
node.type === 'juxtaposing' ||
node.type === 'boolean-expr'
) {
throw new Error('unexpected range modifier');
}

Expand Down Expand Up @@ -327,7 +358,12 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
}
} else if (token.type === '#') {
const node = lastParsedNode(ast);
if (!node || node.type === 'juxtaposing' || isLowLevelGroup(node)) {
if (
!node ||
node.type === 'juxtaposing' ||
isLowLevelGroup(node) ||
node.type === 'boolean-expr'
) {
throw new Error('unexpected list modifier');
}
node.multipliers ??= {};
Expand Down
Loading