Skip to content
Draft
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
23 changes: 14 additions & 9 deletions packages/css-value-parser/src/test/css-value-syntax.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { parseValueSyntax } from '@tokey/css-value-parser';
import { expect } from 'chai';
//TODO: fixme
import {
bar,
dataType,
Expand All @@ -15,21 +14,13 @@ import {
import specs from '@webref/css/css.json' with { type: 'json' };

describe(`sanity`, () => {
const knownProblemticValuespacesCases = [
`custom-selector: <custom-arg>? : <extension-name> [ ( <custom-arg>+#? ) ]?`,
`if-condition: <boolean-expr[ <if-test> ]> | else`,
`cursor-image: [ <url> | <url-set> ] <number>{2}?`,
];
for (const [specName, data] of Object.entries(specs)) {
describe(specName, () => {
for (const { name, syntax } of data) {
if (!syntax) {
continue;
}
const title = `${name}: ${syntax}`;
if (knownProblemticValuespacesCases.includes(title)) {
continue;
}
it(title, () => {
parseValueSyntax(syntax);
});
Expand Down Expand Up @@ -62,6 +53,12 @@ describe('value-syntax-parser', () => {
property('name', [-Infinity, Infinity]),
);
});

it('should parse data-type with type constraint', () => {
expect(parseValueSyntax(`<boolean-expr[ <if-test> ]>`)).to.eql(
dataType('boolean-expr[ <if-test> ]'),
);
});
});

describe('literals/keyword', () => {
Expand Down Expand Up @@ -166,6 +163,14 @@ describe('value-syntax-parser', () => {
expect(parseValueSyntax(`[a]{2}`)).to.eql(group([keyword('a')], { range: [2, 2] }));
expect(parseValueSyntax(`[a]{2, 4}`)).to.eql(group([keyword('a')], { range: [2, 4] }));
});
it('optional modifier after range multiplier', () => {
expect(parseValueSyntax(`<name>{2}?`)).to.eql(
dataType('name', undefined, { range: [0, 2] }),
);
expect(parseValueSyntax(`<name>+#?`)).to.eql(
dataType('name', undefined, { range: [0, Infinity], list: true }),
);
});
});

describe('combinators', () => {
Expand Down
65 changes: 51 additions & 14 deletions packages/css-value-parser/src/value-syntax-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
throw new Error('missing data type name');
}
s.back();
const name = getText(nameBlock, undefined, undefined, source);
let name = getText(nameBlock, undefined, undefined, source);

const type = getLiteralValueType(name);
let range: Range | undefined;
Expand All @@ -205,18 +205,49 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
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)];
// Check if content after [ is a numeric range or a type constraint
let peekOffset = 1;
while (s.peek(peekOffset).type === 'space') peekOffset++;
const firstContentToken = s.peek(peekOffset);

if (firstContentToken.type === 'text') {
// Numeric range: [min, max]
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 closeAngle = s.eat('space').take('>');
if (closeAngle) {
closed = true;
}
} else {
throw new Error('Invalid range');
}
const t = s.eat('space').take('>');
if (t) {
closed = true;
// Type constraint like [ <if-test> ]
let depth = 1;
const bracketStart = t.start;
let bracketEnd = t.end;
while (depth > 0) {
const tok = s.next();
if (!tok.type) {
throw new Error('missing "]"');
}
if (tok.type === '[') depth++;
if (tok.type === ']') {
depth--;
if (depth === 0) {
bracketEnd = tok.end;
}
}
}
name = `${name}${source.substring(bracketStart, bracketEnd)}`;
const closeAngle = s.eat('space').take('>');
if (closeAngle) {
closed = true;
}
}
}
}
Expand Down Expand Up @@ -280,9 +311,15 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) {
}
node.multipliers ??= {};
if (node.multipliers.range) {
throw new Error('multiple multipliers on same node');
if (token.type === '?') {
// ? after existing range modifier makes it optional (min becomes 0)
node.multipliers.range = [0, node.multipliers.range[1]];
} else {
throw new Error('multiple multipliers on same node');
}
} else {
node.multipliers.range = typeToRange(token.type);
}
node.multipliers.range = typeToRange(token.type);
} else if (token.type === '{') {
if (s.peekBack().type === 'space') {
ast.push(literal(token.value, false));
Expand Down