Skip to content

Commit

Permalink
The previous implementation prevented declaring:
Browse files Browse the repository at this point in the history
- a property with a CSS-wide keyword that is not the sole component
  value, eg. "initial identifier" for a hypothetical property defined
  with <ident>+
- a property or descriptor with an arbitrary substitution that is not
  allowed in the context, eg. "size: var(..., attr(...))", which is
  inappropriate (w3c/csswg-drafts#10337)
- a custom property with a <whole-value> value that would be invalid for
  a standard property, eg. "--custom: not first-valid(whole-value)",
  which is inappropriate (w3c/csswg-drafts#10340)
  • Loading branch information
cdoublev committed Oct 7, 2024
1 parent b7c7ce2 commit 1225c7a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 52 deletions.
24 changes: 11 additions & 13 deletions __tests__/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,6 @@ describe('CSSFontFaceDescriptors', () => {
const invalid = [
// Substitution accepted in element-dependent context
['attr(name)'],
['env(name, attr(name))'],
['random-item(--key, 1)', 'random-item(--key, 1%)'],
['mix(50%, 1, 1)', 'mix(50%, 1%, 1%)'],
['toggle(1, 1)', 'toggle(1%, 1%)'],
Expand Down Expand Up @@ -361,10 +360,10 @@ describe('CSSFontFaceDescriptors', () => {
expect(style.fontWidth).toBe('condensed')

// Substitution accepted in any context
style.fontWeight = 'env(name)'
style.sizeAdjust = 'env(name)'
expect(style.fontWeight).toBe('env(name)')
expect(style.sizeAdjust).toBe('env(name)')
style.fontWeight = 'env(name, attr(name))'
style.sizeAdjust = 'env(name, attr(name))'
expect(style.fontWeight).toBe('env(name, attr(name))')
expect(style.sizeAdjust).toBe('env(name, attr(name))')
style.fontWeight = 'first-valid(1)'
style.sizeAdjust = 'first-valid(1%)'
expect(style.fontWeight).toBe('first-valid(1)')
Expand Down Expand Up @@ -516,7 +515,6 @@ describe('CSSPageDescriptors', () => {
const invalid = [
// Substitution accepted in element-dependent context
['attr(name)'],
['env(name, attr(name))'],
['random-item(--key, 1)', 'random-item(--key, 1px)'],
['mix(50%, 1, 1)', 'mix(50%, 1px, 1px)'],
['toggle(1, 1)', 'toggle(1px, 1px)'],
Expand Down Expand Up @@ -545,10 +543,10 @@ describe('CSSPageDescriptors', () => {
expect(style.getPropertyPriority('size')).toBe('important')

// Substitution accepted in any context
style.fontWeight = 'env(name)'
style.size = 'env(name)'
expect(style.fontWeight).toBe('env(name)')
expect(style.size).toBe('env(name)')
style.fontWeight = 'env(name, attr(name))'
style.size = 'env(name, attr(name))'
expect(style.fontWeight).toBe('env(name, attr(name))')
expect(style.size).toBe('env(name, attr(name))')
style.fontWeight = 'first-valid(1)'
style.size = 'first-valid(1px)'
expect(style.fontWeight).toBe('first-valid(1)')
Expand Down Expand Up @@ -728,14 +726,14 @@ describe('--*', () => {
const style = createStyleBlock()
const valid = [
// Whitespaces and comments
[' /**/ Red , ( orange /**/ ) , green /**/ ', 'Red , ( orange /**/ ) , green'],
// Guaranteed-invalid value (initial)
[' /**/ ', ''],
[''],
[' /**/ ', ''],
[' /**/ Red , ( orange /**/ ) , green /**/ ', 'Red , ( orange /**/ ) , green'],
// Substitutions
['var( --PROPerty, /**/ 1e0 /**/ ) ', 'var( --PROPerty, /**/ 1e0 /**/ )'],
['mix(50,/**/, 1e0 ) ', 'mix(50,/**/, 1e0 )'],
['initial'],
['initial initial'],
]
valid.forEach(([input, expected = input]) => {
style.cssText = `--custom: ${input}`
Expand Down
74 changes: 35 additions & 39 deletions lib/parse/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,16 +495,14 @@ function parseCSSArbitrarySubstitution(input, names, context) {
for (let component of input) {
const { name, types: [type], value } = component
if (type === '<function>' || type === '<simple-block>') {
if (names.includes(name)) {
component = parseCSSValue([component], `<${name}()>`, context)
if (component === null || component instanceof SyntaxError) {
return error({ message: 'Invalid substitution' })
}
match = true
} else if (substitutions.arbitrary.some(definition => definition.name === name)) {
return error({ message: 'Invalid substitution' })
}
if (type === '<function>') {
if (names.includes(name)) {
component = parseCSSValue([component], `<${name}()>`, context)
if (component === null || component instanceof SyntaxError) {
return error({ message: 'Invalid substitution' })
}
match = true
}
context = { ...context, function: { context, input } }
}
const substitution = parseCSSArbitrarySubstitution(new Stream(value), names, context)
Expand All @@ -523,41 +521,18 @@ function parseCSSArbitrarySubstitution(input, names, context) {

/**
* @param {Stream} input
* @param {string} type
* @param {object} context
* @returns {SyntaxError|object|object[]|null}
*/
function parseCSSValueSubstitution(input, type, context) {
if (type !== 'descriptor' && type !== 'property') {
return null
}
const { rule: { definition: { cascading, elemental, name } }, trees } = context
function parseCSSValueSubstitution(input, context) {
const { rule: { definition: { cascading, elemental } } } = context
// Try parsing CSS-wide keyword
if (cascading || elemental) {
const match = parseCSSValue(input, substitutions.keywords.join(' | '), context, 'greedy')
if (match) {
return match
}
}
// Try parsing arbitrary substitution
if (trees.length === 0) {
const arbitrary = elemental
? substitutions.arbitrary
: substitutions.arbitrary.filter(({ cascade, element }) => {
if (element) {
return false
}
if (cascade) {
return cascading || name === '@function'
}
return true
})
const names = arbitrary.map(definition => definition.name)
const match = parseCSSArbitrarySubstitution(input, names, context)
if (match) {
return match
}
}
// Try parsing <whole-value> function
const whole = elemental
? substitutions.whole
Expand All @@ -583,11 +558,28 @@ function parseCSSValue(input, definition, context, strategy = 'backtrack') {
}

const root = grammar.create(definition, input, { ...context, separator: ' ' })
const { forgiving, trees } = context
const { definition: { type } } = root
const { forgiving, rule, trees } = context

const substitution = parseCSSValueSubstitution(input, root.definition.type, context)
if (substitution) {
return substitution
// Try parsing arbitrary substitutions
if (trees.length === 0 && (type === 'descriptor' || type === 'property')) {
const { definition: { cascading, elemental, name } } = rule
const definitions = elemental
? substitutions.arbitrary
: substitutions.arbitrary.filter(({ cascade, element }) => {
if (element) {
return false
}
if (cascade) {
return cascading || name === '@function'
}
return true
})
const names = definitions.map(definition => definition.name)
const substitution = parseCSSArbitrarySubstitution(input, names, context)
if (substitution) {
return substitution
}
}

trees.push(root)
Expand Down Expand Up @@ -621,8 +613,12 @@ function parseCSSValue(input, definition, context, strategy = 'backtrack') {
case 'aborted':
return forgiving ? null : match
// The current list is invalid (match is null or undefined)
case 'rejected':
case 'rejected': {
if (type === 'descriptor' || type === 'property') {
return parseCSSValueSubstitution(input, context)
}
return null
}
default:
return match
}
Expand Down

0 comments on commit 1225c7a

Please sign in to comment.