Skip to content

Commit 908c613

Browse files
committed
Refactorize context
The previous context model exposed the closest function and block, but did not allow determining which was the closest. Representing a block is useless at the moment because it has no semantic value, but it defines the token marking the end of its value, which will be required in a future commit. Overall, the new model is simpler, more functional and idiomatic.
1 parent 49c8821 commit 908c613

File tree

10 files changed

+265
-178
lines changed

10 files changed

+265
-178
lines changed

__tests__/value.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -867,20 +867,20 @@ describe('<declaration>', () => {
867867
'--custom: }',
868868
'--custom: !',
869869
]
870-
invalid.forEach(input => expect(parse('<declaration>', input, false)).toBeNull())
870+
invalid.forEach(input => expect(parse('<declaration>', input, false, styleRule)).toBeNull())
871871
})
872872
test('representation', () => {
873-
expect(parse('<declaration>', 'color: green !important', false)).toMatchObject({
873+
expect(parse('<declaration>', 'color: green !important', false, styleRule)).toMatchObject({
874874
important: true,
875875
name: 'color',
876876
types: ['<declaration>'],
877877
value: keyword('green', ['<named-color>', '<color-base>', '<color>', 'color']),
878878
})
879879
})
880880
test('valid', () => {
881-
expect(parse('<declaration>', '--custom: /**/ {} 1e0 {} !important /**/ '))
881+
expect(parse('<declaration>', '--custom: /**/ {} 1e0 {} !important /**/ ', true, styleRule))
882882
.toBe('--custom: {} 1e0 {} !important')
883-
expect(parse('<declaration>', '--custom:'))
883+
expect(parse('<declaration>', '--custom:', true, styleRule))
884884
.toBe('--custom: ')
885885
})
886886
})
@@ -3160,7 +3160,8 @@ describe('<counter>', () => {
31603160
})
31613161
})
31623162
test('valid', () => {
3163-
const context = { ...createContext(styleRule), declaration: { definition: { name: 'any-property' } } }
3163+
let context = createContext(styleRule)
3164+
context = { ...context, context, definition: { name: 'any-property', type: 'declaration' } }
31643165
expect(parse('<counter>', 'counter(chapters, decimal)', true, context)).toBe('counter(chapters)')
31653166
expect(parse('<counter>', 'counters(chapters, "-", decimal)', true, context)).toBe('counters(chapters, "-")')
31663167
})
@@ -3183,7 +3184,8 @@ describe('<counter-style-name>', () => {
31833184
.toMatchObject(customIdent('custom', ['<counter-style-name>']))
31843185
})
31853186
test('valid', () => {
3186-
const context = { ...createContext(styleRule), declaration: { definition: { name: 'any-property' } } }
3187+
let context = createContext(styleRule)
3188+
context = { ...context, context, definition: { name: 'any-property', type: 'declaration' } }
31873189
expect(parse('<counter-style-name>', 'DECIMAL', true, context)).toBe('decimal')
31883190
expect(parse('<counter-style-name>', 'NAME')).toBe('NAME')
31893191
})
@@ -3222,22 +3224,24 @@ describe('<family-name>', () => {
32223224
const invalid = [
32233225
[
32243226
[
3225-
{ declaration: { definition: { name: 'font-family' } } },
3226-
{ declaration: { definition: { name: 'src' } } },
3227-
{ rule: { definition: { name: '@font-feature-values', type: 'rule' } } },
3227+
[createContext(styleRule), 'font-family'],
3228+
[createContext('@font-face'), 'src'],
3229+
[createContext('@font-feature-values')],
32283230
],
32293231
// <generic-family>, <system-family-name>
32303232
['SERIF', 'caption'],
32313233
],
32323234
[
3233-
[{ declaration: { definition: { name: 'voice-family' } } }],
3235+
[[createContext(styleRule), 'voice-family']],
32343236
// <gender>, preserve
32353237
['MALE', 'preserve'],
32363238
],
32373239
]
32383240
invalid.forEach(([contexts, inputs]) =>
3239-
contexts.forEach(context => {
3240-
context = { ...createContext(), ...context }
3241+
contexts.forEach(([context, name]) => {
3242+
if (name) {
3243+
context = { ...context, context, definition: { name, type: 'declaration' } }
3244+
}
32413245
inputs.forEach(input => expect(parse('<family-name>', input, false, context)).toBeNull())
32423246
}))
32433247
})

lib/parse/arbitrary.js

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
const { isBlock, isComma, isDelimiter, isWhitespace } = require('../utils/value.js')
3+
const { getFunction, getRule } = require('../utils/context.js')
34
const { list, omitted } = require('../values/value.js')
45
const Stream = require('./stream.js')
56
const blocks = require('../values/blocks.js')
@@ -53,19 +54,11 @@ function matchAnyValue(input, restricted, stops = []) {
5354
* @returns {object}
5455
* @see {@link https://drafts.csswg.org/css-syntax-3/#typedef-block-contents}
5556
*/
56-
function matchBlockContents({ context, input }, parser) {
57-
if (context.rule) {
58-
return parser.parseBlockContents(input, context)
57+
function matchBlockContents(node, parser) {
58+
if (getRule(node)) {
59+
return parser.parseBlockContents(node.input, node)
5960
}
60-
return parser.parseStyleSheet(input, context)
61-
}
62-
63-
/**
64-
* @param {object} node
65-
* @returns {boolean}
66-
*/
67-
function isGeneralFunctionalNotation({ definition, input }) {
68-
return !definition.name && !input.current.name.startsWith('--')
61+
return parser.parseStyleSheet(node.input, node)
6962
}
7063

7164
/**
@@ -74,8 +67,10 @@ function isGeneralFunctionalNotation({ definition, input }) {
7467
* @returns {SyntaxError|object[]|null}
7568
*/
7669
function matchCommaContainingValue(node, match) {
77-
const { context, input, parent } = node
78-
if (context.function && !isGeneralFunctionalNotation(context.function)) {
70+
const { input, parent } = node
71+
const context = getFunction(node)
72+
// Do not restrict functions defined with a general notation except custom function definitions
73+
if (context && (context.definition.name || context.input.current.name.startsWith('--'))) {
7974
if (input.atEnd()) {
8075
return null
8176
}
@@ -90,7 +85,7 @@ function matchCommaContainingValue(node, match) {
9085
}
9186
return error(node)
9287
}
93-
if (context.function.definition.name !== 'var') {
88+
if (context.definition.name !== 'var') {
9489
return match(input, true)
9590
}
9691
}

lib/parse/grammar.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const actions = require('./actions.js')
88
const arbitrary = require('./arbitrary.js')
99
const forgiving = require('../values/forgiving.js')
1010
const parseDefinition = require('./definition.js')
11+
const { type } = require('../utils/node.js')
1112

1213
const states = {
1314
aborted: {},
@@ -191,7 +192,7 @@ function match(node, parser) {
191192
case 'block':
192193
return input.consume(matchBlock, node, parser)
193194
case 'forgiving':
194-
return parser.parseCSSGrammarList(input, forgiving[name], { ...context, forgiving: true })
195+
return parser.parseCSSGrammarList(input, forgiving[name], node)
195196
case 'function':
196197
return input.consume(matchFunction, node, parser)
197198
case 'omitted':
@@ -210,12 +211,9 @@ function match(node, parser) {
210211
* @returns {object|null}
211212
*/
212213
function matchBlock(value, node, parser) {
213-
const { context, definition, input } = node
214+
const { definition, input } = node
214215
if (isBlock(value, definition.associatedToken)) {
215-
const match = parser.parseCSSGrammar(
216-
new Stream(value.value, input.source),
217-
definition.value,
218-
context)
216+
const match = parser.parseCSSGrammar(new Stream(value.value, input.source), definition.value, node)
219217
if (isFailure(match)) {
220218
return match
221219
}
@@ -234,20 +232,16 @@ function matchFunction(value, node, parser) {
234232
if (value.types[0] !== '<function>') {
235233
return null
236234
}
237-
const { context, definition, input } = node
238-
const ctx = { ...context, function: node }
235+
const { definition, input } = node
239236
if (definition.name) {
240-
const match = parser.parseCSSGrammar(value.name, definition.name, ctx)
237+
const match = parser.parseCSSGrammar(value.name, definition.name, node)
241238
if (isFailure(match)) {
242239
return match
243240
}
244241
value = { ...value, name: match.value }
245242
}
246243
if (definition.value) {
247-
const match = parser.parseCSSGrammar(
248-
new Stream(value.value, input.source),
249-
definition.value,
250-
ctx)
244+
const match = parser.parseCSSGrammar(new Stream(value.value, input.source), definition.value, node)
251245
if (isFailure(match)) {
252246
return match
253247
}
@@ -549,6 +543,7 @@ function create(definition, input, context, parent = null) {
549543
location: input.index,
550544
parent,
551545
state: 'waiting',
546+
type,
552547
}
553548
return actions.configure[definition.name]?.(node) ?? node
554549
}

lib/parse/intercept.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
const { findContext } = require('../utils/context.js')
3+
24
/**
35
* @param {SyntaxError} error
46
* @param {object} node
@@ -8,8 +10,9 @@
810
* It stops propagating an error resulting from parsing an item of a forgiving
911
* selector list, to serialize the list with the invalid selector.
1012
*/
11-
function interceptInvalidSelector(error, { context, input }) {
12-
if (!context.strict && context.forgiving) {
13+
function interceptInvalidSelector(error, node) {
14+
const { context, input, location } = node
15+
if (!context.strict && findContext(node, node => node.definition.type === 'forgiving')) {
1316
input.moveToEnd()
1417
return input.data
1518
}
@@ -30,9 +33,10 @@ function interceptInvalidSelector(error, { context, input }) {
3033
* It stops propagating an error resulting from parsing a query in parens, to
3134
* fallback with <general-enclosed>.
3235
*/
33-
function interceptQueryInParensError(error, { context, input, location }, parser) {
36+
function interceptQueryInParensError(error, node, parser) {
37+
const { input, location } = node
3438
input.moveTo(location)
35-
return parser.parseCSSGrammar(input, '<general-enclosed>', context, 'lazy') ?? error
39+
return parser.parseCSSGrammar(input, '<general-enclosed>', node, 'lazy') ?? error
3640
}
3741

3842
module.exports = {

0 commit comments

Comments
 (0)