Skip to content

Commit 824119b

Browse files
committed
Refactorize substitution functions parsing
All substitution functions are now parsed more consistently. The name is parsed against a grammar instead of simply matching an identifier, which adds support for any aliased/mapped names. The context is then validated to return an error when it is missing a dependency for the substitution, except for arbitrary substitutions, to allow declaration value definitions like @property/initial-value to match them. The input is then parsed against the corresponding production, to allow applying specific transition actions.
1 parent aed1b6c commit 824119b

File tree

3 files changed

+71
-63
lines changed

3 files changed

+71
-63
lines changed

lib/parse/parser.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -953,14 +953,14 @@ function parseCSSArbitrarySubstitution(value, context) {
953953
return null
954954
}
955955
const { definition: { cascading, elemental } } = getRule(context)
956-
for (const { cascade, element, name, type = `<${name}()>` } of substitutions.arbitrary) {
956+
for (const { cascade, definition, element, name } of substitutions.arbitrary) {
957957
if (!elemental && (element || (cascade && !cascading))) {
958958
continue
959959
}
960960
if (isFailure(parseCSSGrammar(value.name, name, context))) {
961961
continue
962962
}
963-
return parseCSSGrammar([value], type, context) ?? error({ message: 'Invalid substitution' })
963+
return parseCSSGrammar([value], definition, context) ?? error({ message: 'Invalid substitution' })
964964
}
965965
return null
966966
}
@@ -1009,19 +1009,25 @@ function parseCSSArbitrarySubstitutionContainingValue(input, context) {
10091009
*/
10101010
function parseCSSValueSubstitution(input, context) {
10111011
const { definition: { elemental } } = getRule(context)
1012-
for (const { element, name, pending } of substitutions.whole) {
1013-
if (elemental || !element) {
1014-
const match = parseCSSGrammar(input, `<${name}()>`, context, 'greedy')
1015-
if (isError(match)) {
1016-
return match
1017-
}
1018-
if (match) {
1019-
if (pending) {
1020-
context.globals.set('pending', true)
1021-
}
1022-
return match
1023-
}
1012+
const next = input.next()
1013+
if (next?.types[0] !== '<function>') {
1014+
return null
1015+
}
1016+
for (const { definition, element, name, pending } of substitutions.whole) {
1017+
if (isFailure(parseCSSGrammar(next.name, name, context))) {
1018+
continue
1019+
}
1020+
if (element && !elemental) {
1021+
return error({ message: 'Invalid substitution' })
1022+
}
1023+
const match = parseCSSGrammar(input, definition, context, 'greedy')
1024+
if (isFailure(match)) {
1025+
return error({ message: 'Invalid substitution' })
1026+
}
1027+
if (pending) {
1028+
context.globals.set('pending', true)
10241029
}
1030+
return match
10251031
}
10261032
return null
10271033
}

lib/parse/replace.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,29 +78,35 @@ function getPercentageResolutionType({ definition: { name }, parent }) {
7878
*/
7979
function replaceNumeric(node, parser) {
8080

81+
const topLevel = !isProducedBy(node, '<calc-value>')
8182
const { context, definition, input, parent } = node
83+
const next = input.next()
84+
85+
if (next?.types[0] !== '<function>') {
86+
if (definition.name === '<number>' && topLevel) {
87+
return replaceWithColorChannelKeyword(node, parser)
88+
}
89+
return null
90+
}
8291

8392
// Do not try replacing <dimension> produced by a CSS dimension type
8493
if (definition.name === '<dimension>' && parent?.definition.type === 'non-terminal') {
8594
return null
8695
}
8796

88-
const name = input.next().name?.toLowerCase()
89-
const topLevel = !isProducedBy(node, '<calc-value>')
90-
91-
const fn = name && substitutions.numeric.find(definition => definition.name === name)
92-
93-
if (fn) {
97+
for (const { definition, element, name } of substitutions.numeric) {
9498

95-
if (fn.element && !getRule(node)?.definition.elemental) {
99+
if (isFailure(parser.parseCSSGrammar(next.name, name, context))) {
100+
continue
101+
}
102+
if (element && !getRule(node)?.definition.elemental) {
96103
return error(node)
97104
}
98105

99106
if (topLevel) {
100107
context.globals.set('calc-terms', 0)
101108
}
102-
103-
const match = parser.parseCSSGrammar(input, `<${name}()>`, node, 'lazy')
109+
const match = parser.parseCSSGrammar(input, definition, node, 'lazy')
104110

105111
if (isFailure(match)) {
106112
return error(node)
@@ -127,10 +133,6 @@ function replaceNumeric(node, parser) {
127133
}
128134
return match
129135
}
130-
// Try replacing <number> at the top-level of a color function
131-
if (definition.name === '<number>' && topLevel) {
132-
return replaceWithColorChannelKeyword(node, parser)
133-
}
134136
return null
135137
}
136138

lib/values/substitutions.js

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ const properties = require('../properties/definitions.js')
1010
* @see {@link https://drafts.csswg.org/css-variables-2/#funcdef-var}
1111
*/
1212
const arbitrary = [
13-
{ name: '<dashed-ident>', element: true, type: '<dashed-function>' },
14-
{ name: 'attr', element: true },
15-
{ name: 'env' },
16-
{ name: 'inherit', cascade: true },
17-
{ name: 'random-item', element: true },
18-
{ name: 'var', cascade: true },
13+
{ definition: '<dashed-function>', element: true, name: '<dashed-ident>' },
14+
{ definition: '<attr()>', element: true, name: 'attr', },
15+
{ definition: '<env()>', name: 'env' },
16+
{ definition: '<inherit()>', cascade: true, name: 'inherit' },
17+
{ definition: '<random-item()>', element: true, name: 'random-item' },
18+
{ definition: '<var()>', cascade: true, name: 'var' },
1919
]
2020

2121
/**
@@ -39,33 +39,33 @@ const image = ['paint']
3939
* @see {@link https://drafts.csswg.org/css-values-5/#funcdef-sibling-index}
4040
*/
4141
const numeric = [
42-
{ name: 'abs' },
43-
{ name: 'acos' },
44-
{ name: 'asin' },
45-
{ name: 'atan' },
46-
{ name: 'atan2' },
47-
{ name: 'calc-interpolate', element: true },
48-
{ name: 'calc-mix' },
49-
{ name: 'calc' },
50-
{ name: 'clamp' },
51-
{ name: 'cos' },
52-
{ name: 'exp' },
53-
{ name: 'hypot' },
54-
{ name: 'log' },
55-
{ name: 'max' },
56-
{ name: 'min' },
57-
{ name: 'mod' },
58-
{ name: 'pow' },
59-
{ name: 'progress' },
60-
{ name: 'random', element: true },
61-
{ name: 'rem' },
62-
{ name: 'round' },
63-
{ name: 'sibling-count', element: true },
64-
{ name: 'sibling-index', element: true },
65-
{ name: 'sign' },
66-
{ name: 'sin' },
67-
{ name: 'sqrt' },
68-
{ name: 'tan' },
42+
{ definition: '<abs()>', name: 'abs' },
43+
{ definition: '<acos()>', name: 'acos' },
44+
{ definition: '<asin()>', name: 'asin' },
45+
{ definition: '<atan()>', name: 'atan' },
46+
{ definition: '<atan2()>', name: 'atan2' },
47+
{ definition: '<calc-interpolate()>', name: 'calc-interpolate', element: true },
48+
{ definition: '<calc-mix()>', name: 'calc-mix' },
49+
{ definition: '<calc()>', name: 'calc' },
50+
{ definition: '<clamp()>', name: 'clamp' },
51+
{ definition: '<cos()>', name: 'cos' },
52+
{ definition: '<exp()>', name: 'exp' },
53+
{ definition: '<hypot()>', name: 'hypot' },
54+
{ definition: '<log()>', name: 'log' },
55+
{ definition: '<max()>', name: 'max' },
56+
{ definition: '<min()>', name: 'min' },
57+
{ definition: '<mod()>', name: 'mod' },
58+
{ definition: '<pow()>', name: 'pow' },
59+
{ definition: '<progress()>', name: 'progress' },
60+
{ definition: '<random()>', name: 'random', element: true },
61+
{ definition: '<rem()>', name: 'rem' },
62+
{ definition: '<round()>', name: 'round' },
63+
{ definition: '<sibling-count()>', name: 'sibling-count', element: true },
64+
{ definition: '<sibling-index()>', name: 'sibling-index', element: true },
65+
{ definition: '<sign()>', name: 'sign' },
66+
{ definition: '<sin()>', name: 'sin' },
67+
{ definition: '<sqrt()>', name: 'sqrt' },
68+
{ definition: '<tan()>', name: 'tan' },
6969
]
7070

7171
/**
@@ -74,9 +74,9 @@ const numeric = [
7474
* @see {@link https://drafts.csswg.org/css-values-5/#funcdef-toggle}
7575
*/
7676
const whole = [
77-
{ name: 'first-valid' },
78-
{ name: 'interpolate', element: true, pending: true },
79-
{ name: 'toggle', element: true, pending: true },
77+
{ definition: '<first-valid()>', name: 'first-valid' },
78+
{ definition: '<interpolate()>', name: 'interpolate', element: true, pending: true },
79+
{ definition: '<toggle()>', name: 'toggle', element: true, pending: true },
8080
]
8181

8282
module.exports = {

0 commit comments

Comments
 (0)