Skip to content

Commit 39d6045

Browse files
authored
Fix @scope parsing to support selector lists (#474)
The `@scope` rule parser only accepted a single selector inside the parentheses, causing a false `) expected` error when using comma-separated selector lists like `@scope (.foo, .bar) { }`. This is valid CSS per the CSS Cascading and Inheritance Level 6 specification, which defines scope-start and scope-end as <selector-list>. Extract a _parseScopeSelectorList() helper that handles comma-separated selectors, matching the existing pattern used in _parsePseudo() for pseudo-class selector lists. Fixes microsoft/vscode#290942
1 parent bf53a6b commit 39d6045

File tree

2 files changed

+20
-4
lines changed

2 files changed

+20
-4
lines changed

src/parser/cssParser.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,10 +1283,10 @@ export class Parser {
12831283
if (this.accept(TokenType.ParenthesisL)) {
12841284
// scope-start selector can start with a combinator as it defaults to :scope
12851285
// Treat as nested
1286-
if (!node.setScopeStart(this._parseSelector(true))) {
1286+
if (!node.setScopeStart(this._parseScopeSelectorList())) {
12871287
return this.finish(node, ParseError.SelectorExpected, [], [TokenType.ParenthesisR])
12881288
}
1289-
1289+
12901290
if (!this.accept(TokenType.ParenthesisR)) {
12911291
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
12921292
}
@@ -1299,10 +1299,10 @@ export class Parser {
12991299
}
13001300
// 'to' selector can start with a combinator as it defaults to :scope
13011301
// Treat as nested
1302-
if (!node.setScopeEnd(this._parseSelector(true))) {
1302+
if (!node.setScopeEnd(this._parseScopeSelectorList())) {
13031303
return this.finish(node, ParseError.SelectorExpected, [], [TokenType.ParenthesisR])
13041304
}
1305-
1305+
13061306
if (!this.accept(TokenType.ParenthesisR)) {
13071307
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
13081308
}
@@ -1311,6 +1311,17 @@ export class Parser {
13111311
return this.finish(node)
13121312
}
13131313

1314+
private _parseScopeSelectorList(): nodes.Node | null {
1315+
const selectors = this.createNode(nodes.NodeType.SelectorList);
1316+
if (!selectors.addChild(this._parseSelector(true))) {
1317+
return null;
1318+
}
1319+
while (this.accept(TokenType.Comma) && selectors.addChild(this._parseSelector(true))) {
1320+
// loop
1321+
}
1322+
return this.finish(selectors);
1323+
}
1324+
13141325
public _parseMedium(): nodes.Node | null {
13151326
const node = this.create(nodes.Node);
13161327
if (node.addChild(this._parseIdent())) {

src/test/css/parser.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ suite('CSS - Parser', () => {
5959
assertNode('@scope (.foo) {}', parser, parser._parseStylesheet.bind(parser))
6060
assertNode('@scope to (.bar) {}', parser, parser._parseStylesheet.bind(parser))
6161
assertNode('@scope (.foo) to (.bar) {}', parser, parser._parseStylesheet.bind(parser))
62+
assertNode('@scope (.foo, .bar) {}', parser, parser._parseStylesheet.bind(parser))
63+
assertNode('@scope (.foo, .bar) to (.baz, .qux) {}', parser, parser._parseStylesheet.bind(parser))
6264
assertNode('@-ms-viewport { width: 320px; height: 768px; }', parser, parser._parseStylesheet.bind(parser));
6365
assertNode('#boo, far {} \n.far boo {}', parser, parser._parseStylesheet.bind(parser));
6466
assertNode('@-moz-keyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser));
@@ -279,6 +281,9 @@ suite('CSS - Parser', () => {
279281
assertNode('@scope to (.bar) { }', parser, parser._parseScope.bind(parser))
280282
assertNode('@scope (.foo) to (.bar) { }', parser, parser._parseScope.bind(parser))
281283
assertNode('@scope (#foo) to (:has(> link)) {}', parser, parser._parseScope.bind(parser))
284+
assertNode('@scope (.foo, .bar) { }', parser, parser._parseScope.bind(parser))
285+
assertNode('@scope to (.foo, .bar) { }', parser, parser._parseScope.bind(parser))
286+
assertNode('@scope (.foo, .bar) to (.baz, .qux) { }', parser, parser._parseScope.bind(parser))
282287

283288
assertError('@scope ( { }', parser, parser._parseScope.bind(parser), ParseError.SelectorExpected)
284289
assertError('@scope () { }', parser, parser._parseScope.bind(parser), ParseError.SelectorExpected)

0 commit comments

Comments
 (0)