diff --git a/src/parser/cssNodes.ts b/src/parser/cssNodes.ts index 7f1cd11b..e41303b0 100644 --- a/src/parser/cssNodes.ts +++ b/src/parser/cssNodes.ts @@ -101,6 +101,7 @@ export enum NodeType { PropertyAtRule, Container, ModuleConfig, + StartingStyleAtRule, } export enum ReferenceType { @@ -1224,6 +1225,17 @@ export class PropertyAtRule extends BodyDeclaration { } +export class StartingStyleAtRule extends BodyDeclaration { + + constructor(offset: number, length: number) { + super(offset, length); + } + + get type(): NodeType { + return NodeType.StartingStyleAtRule; + } +} + export class Document extends BodyDeclaration { constructor(offset: number, length: number) { diff --git a/src/parser/cssParser.ts b/src/parser/cssParser.ts index 37ea6f6a..96b2339a 100644 --- a/src/parser/cssParser.ts +++ b/src/parser/cssParser.ts @@ -326,6 +326,7 @@ export class Parser { || this._parseNamespace() || this._parseDocument() || this._parseContainer(isNested) + || this._parseStartingStyleAtRule(isNested) || this._parseUnknownAtRule(); } @@ -366,6 +367,7 @@ export class Parser { || this._parseSupports(true) || this._parseLayer(true) || this._parseContainer(true) + || this._parseStartingStyleAtRule(true) || this._parseUnknownAtRule(); } @@ -901,6 +903,30 @@ export class Parser { return this._parseBody(node, this._parseDeclaration.bind(this)); } + _parseStartingStyleAtRule(isNested = false) { + if (!this.peekKeyword("@starting-style")) { + return null; + } + + const node = this.create(nodes.StartingStyleAtRule); + this.consumeToken() // @starting-style + + return this._parseBody(node, this._parseStartingStyleDeclaration.bind(this, isNested)); + } + + // this method is the same as ._parseContainerDeclaration() + // which is the same as ._parseMediaDeclaration(), + // _parseSupportsDeclaration, and ._parseLayerDeclaration() + _parseStartingStyleDeclaration(isNested = false) { + if (isNested) { + // if nested, the body can contain rulesets, but also declarations + return this._tryParseRuleset(true) + || this._tryToParseDeclaration() + || this._parseStylesheetStatement(true); + } + return this._parseStylesheetStatement(false); + } + public _parseLayer(isNested: boolean = false): nodes.Node | null { // @layer layer-name {rules} // @layer layer-name; diff --git a/src/test/css/nodes.test.ts b/src/test/css/nodes.test.ts index d2793761..85504e42 100644 --- a/src/test/css/nodes.test.ts +++ b/src/test/css/nodes.test.ts @@ -135,6 +135,15 @@ suite('CSS - Nodes', () => { assertNodes(fn, '@keyframes name { from { top: 0px} to { top: 100px } }', 'keyframe,identifier,...,keyframeselector,...,declaration,...,keyframeselector,...,declaration,...'); }); + test('Starting-style', function () { + function fn(input: string): nodes.Node { + let parser = new Parser(); + let node = parser.internalParse(input, parser._parseStartingStyleAtRule)!; + return node; + } + assertNodes(fn, '@starting-style { p { opacity: 0; } }', 'startingstyleatrule,declarations,ruleset,...,selector,...,elementnameselector,...,...,...,...,...,...,...,...,...'); + }); + test('UnicodeRange', function () { function fn(input: string): nodes.Node { let parser = new Parser(); diff --git a/src/test/css/parser.test.ts b/src/test/css/parser.test.ts index 95be3613..b3b1fec6 100644 --- a/src/test/css/parser.test.ts +++ b/src/test/css/parser.test.ts @@ -177,6 +177,15 @@ suite('CSS - Parser', () => { assertNode(`@container card (inline-size > 30em) { @container style(--responsive: true) {} }`, parser, parser._parseStylesheet.bind(parser)); }); + test('@starting-style', function () { + const parser = new Parser(); + // These assertions would still hold if @starting-style blocks were being processed as unknown at-rules + // Parsing into the expected AST is instead tested in nodes.test.ts + assertNode(`@starting-style { p { background-color: skyblue; } }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`p { @starting-style { background-color: skyblue; } }`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`p { @starting-style { @layer {} } }`, parser, parser._parseStylesheet.bind(parser)); + }); + test('@container query length units', function () { const parser = new Parser(); assertNode(`@container (min-width: 700px) { .card h2 { font-size: max(1.5em, 1.23em + 2cqi); } }`, parser, parser._parseStylesheet.bind(parser));