Skip to content

Commit

Permalink
support for @container
Browse files Browse the repository at this point in the history
  • Loading branch information
aeschli committed Oct 5, 2023
1 parent 2ff984e commit 7204656
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/parser/cssNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export enum NodeType {
Layer,
LayerNameList,
LayerName,
PropertyAtRule
PropertyAtRule,
Container
}

export enum ReferenceType {
Expand Down Expand Up @@ -1239,6 +1240,17 @@ export class Document extends BodyDeclaration {
}
}

export class Container extends BodyDeclaration {

constructor(offset: number, length: number) {
super(offset, length);
}

public get type(): NodeType {
return NodeType.Container;
}
}

export class Medialist extends Node {
constructor(offset: number, length: number) {
super(offset, length);
Expand Down
105 changes: 105 additions & 0 deletions src/parser/cssParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export class Parser {
|| this._parseViewPort()
|| this._parseNamespace()
|| this._parseDocument()
|| this._parseContainer()
|| this._parseUnknownAtRule();
}

Expand Down Expand Up @@ -1292,6 +1293,110 @@ export class Parser {
return this._parseBody(node, this._parseStylesheetStatement.bind(this));
}

public _parseContainer(): nodes.Node | null {
if (!this.peekKeyword('@container')) {
return null;
}
const node = this.create(nodes.Container);
this.consumeToken(); // @container

node.addChild(this._parseIdent()); // optional container name
node.addChild(this._parseContainerQuery());

return this._parseBody(node, this._parseStylesheetStatement.bind(this));
}

public _parseContainerQuery(): nodes.Node | null {
// <container-query> = not <query-in-parens>
// | <query-in-parens> [ [ and <query-in-parens> ]* | [ or <query-in-parens> ]* ]
const node = this.create(nodes.Node);
if (this.acceptIdent('not')) {
node.addChild(this._parseContainerQueryInParens());
} else {
node.addChild(this._parseContainerQueryInParens());
if (this.peekIdent('and')) {
while (this.acceptIdent('and')) {
node.addChild(this._parseContainerQueryInParens());
}
} else if (this.peekIdent('or')) {
while (this.acceptIdent('or')) {
node.addChild(this._parseContainerQueryInParens());
}
}
}
return this.finish(node);
}

public _parseContainerQueryInParens(): nodes.Node {
// <query-in-parens> = ( <container-query> )
// | ( <size-feature> )
// | style( <style-query> )
// | <general-enclosed>
const node = this.create(nodes.Node);
if (this.accept(TokenType.ParenthesisL)) {
if (this.peekIdent('not') || this.peek(TokenType.ParenthesisL)) {
node.addChild(this._parseContainerQuery());
} else {
node.addChild(this._parseMediaFeature());
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
}
} else if (this.acceptIdent('style')) {
if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]);
}
node.addChild(this._parseStyleQuery());
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
}
} else {
return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]);
}
return this.finish(node);
}

public _parseStyleQuery(): nodes.Node {
// <style-query> = not <style-in-parens>
// | <style-in-parens> [ [ and <style-in-parens> ]* | [ or <style-in-parens> ]* ]
// | <style-feature>
// <style-in-parens> = ( <style-query> )
// | ( <style-feature> )
// | <general-enclosed>
const node = this.create(nodes.Node);

if (this.acceptIdent('not')) {
node.addChild(this._parseStyleInParens());
} else if (this.peek(TokenType.ParenthesisL)) {
node.addChild(this._parseStyleInParens());
if (this.peekIdent('and')) {
while (this.acceptIdent('and')) {
node.addChild(this._parseStyleInParens());
}
} else if (this.peekIdent('or')) {
while (this.acceptIdent('or')) {
node.addChild(this._parseStyleInParens());
}
}
} else {
node.addChild(this._parseDeclaration([TokenType.ParenthesisR]));
}
return this.finish(node);
}

public _parseStyleInParens(): nodes.Node {
const node = this.create(nodes.Node);
if (this.accept(TokenType.ParenthesisL)) {
node.addChild(this._parseStyleQuery());
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
}
} else {
return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]);
}
return this.finish(node);
}

// https://www.w3.org/TR/css-syntax-3/#consume-an-at-rule
public _parseUnknownAtRule(): nodes.Node | null {
if (!this.peek(TokenType.AtKeyword)) {
Expand Down
7 changes: 7 additions & 0 deletions src/test/css/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ suite('CSS - Parser', () => {
assertError(`@property { }`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
});

test('@container', function () {
const parser = new Parser();
assertNode(`@container (width <= 150px) { #inner { background-color: skyblue; }}`, parser, parser._parseStylesheet.bind(parser));
assertNode(`@container card (inline-size > 30em) and style(--responsive: true) { }`, parser, parser._parseStylesheet.bind(parser));
assertNode(`@container card (inline-size > 30em) { @container style(--responsive: true) {} }`, parser, parser._parseStylesheet.bind(parser));
});

test('@import', function () {
const parser = new Parser();
assertNode('@import "asdasdsa"', parser, parser._parseImport.bind(parser));
Expand Down

0 comments on commit 7204656

Please sign in to comment.