From 37319b7b64a17515f988aaaf5f16fff95e9f0fa5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:42:14 +0000 Subject: [PATCH 1/3] Initial plan From fe6623eed7d711b352e66be2a948a09e6673d070 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:54:26 +0000 Subject: [PATCH 2/3] Fix value syntax parser to handle parameterized data types and optional multiplier after range Co-authored-by: barak007 <2240471+barak007@users.noreply.github.com> --- .../src/test/css-value-syntax.spec.ts | 23 ++++--- .../src/value-syntax-parser.ts | 65 +++++++++++++++---- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/packages/css-value-parser/src/test/css-value-syntax.spec.ts b/packages/css-value-parser/src/test/css-value-syntax.spec.ts index 702f57bf..659ca493 100644 --- a/packages/css-value-parser/src/test/css-value-syntax.spec.ts +++ b/packages/css-value-parser/src/test/css-value-syntax.spec.ts @@ -1,6 +1,5 @@ import { parseValueSyntax } from '@tokey/css-value-parser'; import { expect } from 'chai'; -//TODO: fixme import { bar, dataType, @@ -15,11 +14,6 @@ import { import specs from '@webref/css/css.json' with { type: 'json' }; describe(`sanity`, () => { - const knownProblemticValuespacesCases = [ - `custom-selector: ? : [ ( +#? ) ]?`, - `if-condition: ]> | else`, - `cursor-image: [ | ] {2}?`, - ]; for (const [specName, data] of Object.entries(specs)) { describe(specName, () => { for (const { name, syntax } of data) { @@ -27,9 +21,6 @@ describe(`sanity`, () => { continue; } const title = `${name}: ${syntax}`; - if (knownProblemticValuespacesCases.includes(title)) { - continue; - } it(title, () => { parseValueSyntax(syntax); }); @@ -62,6 +53,12 @@ describe('value-syntax-parser', () => { property('name', [-Infinity, Infinity]), ); }); + + it('should parse data-type with type constraint', () => { + expect(parseValueSyntax(` ]>`)).to.eql( + dataType('boolean-expr[ ]'), + ); + }); }); describe('literals/keyword', () => { @@ -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(`{2}?`)).to.eql( + dataType('name', undefined, { range: [0, 2] }), + ); + expect(parseValueSyntax(`+#?`)).to.eql( + dataType('name', undefined, { range: [0, Infinity], list: true }), + ); + }); }); describe('combinators', () => { diff --git a/packages/css-value-parser/src/value-syntax-parser.ts b/packages/css-value-parser/src/value-syntax-parser.ts index a91f632c..40a9fd89 100644 --- a/packages/css-value-parser/src/value-syntax-parser.ts +++ b/packages/css-value-parser/src/value-syntax-parser.ts @@ -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; @@ -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 [ ] + 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; + } } } } @@ -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)); From d951cab291fc58e01413c4f1f6401f3b6c72dd6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:55:56 +0000 Subject: [PATCH 3/3] Use template literal for string concatenation in parameterized type name Co-authored-by: barak007 <2240471+barak007@users.noreply.github.com> --- packages/css-value-parser/src/value-syntax-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/css-value-parser/src/value-syntax-parser.ts b/packages/css-value-parser/src/value-syntax-parser.ts index 40a9fd89..24b536e7 100644 --- a/packages/css-value-parser/src/value-syntax-parser.ts +++ b/packages/css-value-parser/src/value-syntax-parser.ts @@ -243,7 +243,7 @@ function parseTokens(tokens: ValueSyntaxToken[], source: string) { } } } - name = name + source.substring(bracketStart, bracketEnd); + name = `${name}${source.substring(bracketStart, bracketEnd)}`; const closeAngle = s.eat('space').take('>'); if (closeAngle) { closed = true;