From f9b216f28820247fd7709c3335d720e61fb047cd Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Sat, 5 Aug 2023 16:39:19 +0100 Subject: [PATCH] fixup broken @page rule parse/write --- crates/hdx_ast/src/css/rules/page.rs | 2 +- .../hdx_parser/src/css/parser_extensions.rs | 2 +- crates/hdx_parser/src/css/rules/mod.rs | 11 + crates/hdx_parser/src/css/rules/page.rs | 109 +++- crates/hdx_writer/src/css/mod.rs | 9 +- .../postcss_parser/atrule-decls.json | 608 ++++++++++++------ .../postcss_parser/atrule-no-params.json | 9 +- 7 files changed, 533 insertions(+), 217 deletions(-) diff --git a/crates/hdx_ast/src/css/rules/page.rs b/crates/hdx_ast/src/css/rules/page.rs index 7ebf3af2..1dea926b 100644 --- a/crates/hdx_ast/src/css/rules/page.rs +++ b/crates/hdx_ast/src/css/rules/page.rs @@ -11,7 +11,7 @@ use crate::{ #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] pub struct PageRule<'a> { #[cfg_attr(feature = "serde", serde(borrow))] - pub selectors: Box<'a, Option>>>, + pub selectors: Box<'a, Spanned>>, #[cfg_attr(feature = "serde", serde(borrow))] pub properties: Box<'a, Vec<'a, Spanned>>>, #[cfg_attr(feature = "serde", serde(borrow))] diff --git a/crates/hdx_parser/src/css/parser_extensions.rs b/crates/hdx_parser/src/css/parser_extensions.rs index 07fe0838..1136963b 100644 --- a/crates/hdx_parser/src/css/parser_extensions.rs +++ b/crates/hdx_parser/src/css/parser_extensions.rs @@ -103,6 +103,7 @@ impl<'a> Parser<'a> { return result; } Kind::LeftCurly => { + dbg!(self.cur()); return self.parse_block( |parser: &mut Parser<'a>, rules: Vec<'a, Spanned>, @@ -113,7 +114,6 @@ impl<'a> Parser<'a> { } _ => { prelude = Some(Prelude::parse(self)?); - self.advance(); } } } diff --git a/crates/hdx_parser/src/css/rules/mod.rs b/crates/hdx_parser/src/css/rules/mod.rs index 79d28612..e1c2c321 100644 --- a/crates/hdx_parser/src/css/rules/mod.rs +++ b/crates/hdx_parser/src/css/rules/mod.rs @@ -1 +1,12 @@ pub mod page; + +use crate::{Kind, Parse, Parser, Result, Spanned}; + +pub struct NoPreludeAllowed; +impl<'a> Parse<'a> for NoPreludeAllowed { + fn parse(parser: &mut Parser<'a>) -> Result> { + let span = parser.cur().span; + parser.expect_without_advance(Kind::LeftCurly)?; + Ok(Self {}.spanned(span.up_to(&parser.cur().span))) + } +} diff --git a/crates/hdx_parser/src/css/rules/page.rs b/crates/hdx_parser/src/css/rules/page.rs index 19834a70..585e4f85 100644 --- a/crates/hdx_parser/src/css/rules/page.rs +++ b/crates/hdx_parser/src/css/rules/page.rs @@ -6,11 +6,13 @@ use hdx_ast::css::{ }; use oxc_allocator::Vec; +use super::NoPreludeAllowed; use crate::{atom, diagnostics, Atom, Atomizable, Kind, Parse, Parser, Result, Spanned}; impl<'a> Parse<'a> for PageRule<'a> { fn parse(parser: &mut Parser<'a>) -> Result> { let span = parser.cur().span; + dbg!(parser.cur()); parser.parse_at_rule( Some(atom!("page")), |parser: &mut Parser<'a>, @@ -19,7 +21,9 @@ impl<'a> Parse<'a> for PageRule<'a> { rules: Vec<'a, Spanned>>, properties: Vec<'a, Spanned>>| { Ok(Self { - selectors: parser.boxup(selectors), + selectors: parser.boxup(selectors.unwrap_or_else(|| { + Spanned::dummy(PageSelectorList { children: parser.new_vec() }) + })), properties: parser.boxup(properties), rules: parser.boxup(rules), } @@ -32,8 +36,9 @@ impl<'a> Parse<'a> for PageRule<'a> { impl<'a> Parse<'a> for PageSelectorList<'a> { fn parse(parser: &mut Parser<'a>) -> Result> { let span = parser.cur().span; - Ok(Self { children: parser.parse_comma_list_of::()? } - .spanned(span.up_to(&parser.cur().span))) + let ok = Ok(Self { children: parser.parse_comma_list_of::()? } + .spanned(span.up_to(&parser.cur().span))); + ok } } @@ -43,20 +48,19 @@ impl<'a> Parse<'a> for PageSelector<'a> { let mut page_type = None; let mut pseudos = parser.new_vec(); if parser.at(Kind::Ident) { - println!("PageSelector::page_type assigning to {:?}", parser.cur()); page_type = Some(parser.expect_ident()?); } else { parser.expect_without_advance(Kind::Colon)?; } if parser.at(Kind::Colon) { loop { + dbg!(parser.cur()); + pseudos.push(PagePseudoClass::parse(parser)?); if !parser.at(Kind::Colon) { break; } - pseudos.push(PagePseudoClass::parse(parser)?); } } - println!("PageSelector::OK(self) {:?} {:?}", page_type, pseudos); Ok(Self { page_type, pseudos }.spanned(span.up_to(&parser.cur().span))) } } @@ -80,7 +84,7 @@ impl<'a> Parse<'a> for PageMarginRule<'a> { None, |parser: &mut Parser<'a>, _name: Atom, - _prelude: Option>, + _prelude: Option>, _rules: Vec<'a, Spanned>>, properties: Vec<'a, Spanned>>| { Ok(Self { margin_box: PageMarginBox::TopLeft, properties } @@ -90,11 +94,90 @@ impl<'a> Parse<'a> for PageMarginRule<'a> { } } -struct IgnoreWhitespaceInPageMarginPrelude; -impl<'a> Parse<'a> for IgnoreWhitespaceInPageMarginPrelude { - fn parse(parser: &mut Parser<'a>) -> Result> { - let span = parser.cur().span; - parser.expect_without_advance(Kind::LeftCurly)?; - Ok(Self {}.spanned(span.up_to(&parser.cur().span))) +#[cfg(test)] +mod test { + use hdx_ast::{ + css::{ + properties::{Background, Property}, + rules::{PagePseudoClass, PageRule, PageSelector, PageSelectorList}, + values::{ColorValue, NamedColor}, + }, + Spanned, + }; + use oxc_allocator::Allocator; + + use crate::{Atom, Parser, ParserOptions, Span, Vec}; + + #[test] + fn parses_toc_left_selector() { + let allocator = Allocator::default(); + let parser = Parser::new(&allocator, "toc:left", ParserOptions::default()); + let parser_return = parser.parse_with::(); + let ast = parser_return.output.unwrap(); + if !parser_return.errors.is_empty() { + panic!("{:?}", parser_return.errors[0]); + } + if !parser_return.warnings.is_empty() { + panic!("{:?}", parser_return.warnings[0]); + } + let mut children = Vec::new_in(&allocator); + let mut pseudos = Vec::new_in(&allocator); + pseudos.push(Spanned { span: Span::new(3, 8), node: PagePseudoClass::Left }); + children.push(Spanned { + span: Span::new(0, 8), + node: PageSelector { page_type: Some(Atom::from("toc")), pseudos }, + }); + assert_eq!(ast, Spanned { span: Span::new(0, 8), node: PageSelectorList { children } }); + } + + #[test] + fn parses_toc_left_page_rule_with_bakcground_black() { + let allocator = Allocator::default(); + let parser = Parser::new( + &allocator, + "@page toc:left { background: black; }", + ParserOptions::default(), + ); + let mut children = Vec::new_in(&allocator); + let mut pseudos = Vec::new_in(&allocator); + pseudos.push(Spanned { span: Span::new(9, 15), node: PagePseudoClass::Left }); + children.push(Spanned { + span: Span::new(6, 15), + node: PageSelector { page_type: Some(Atom::from("toc")), pseudos }, + }); + let mut properties = Vec::new_in(&allocator); + properties.push(Spanned { + span: Span::new(17, 36), + node: Property::Background({ + parser.boxup(Spanned { + span: Span::new(17, 36), + node: Background { + value: parser.boxup(Spanned { + span: Span::new(29, 34), + node: ColorValue::Named(NamedColor::Black), + }), + important: false, + }, + }) + }), + }); + let expected = Spanned { + span: Span::new(0, 37), + node: PageRule { + selectors: parser + .boxup(Spanned { span: Span::new(6, 15), node: PageSelectorList { children } }), + properties: parser.boxup(properties), + rules: parser.boxup(Vec::new_in(&allocator)), + }, + }; + let parser_return = parser.parse_entirely_with::(); + if !parser_return.errors.is_empty() { + panic!("{:?}", parser_return.errors[0]); + } + if !parser_return.warnings.is_empty() { + panic!("{:?}", parser_return.warnings[0]); + } + let ast = parser_return.output.unwrap(); + assert_eq!(ast, expected); } } diff --git a/crates/hdx_writer/src/css/mod.rs b/crates/hdx_writer/src/css/mod.rs index e03a6de0..b3fe7495 100644 --- a/crates/hdx_writer/src/css/mod.rs +++ b/crates/hdx_writer/src/css/mod.rs @@ -78,9 +78,11 @@ impl<'a> WriteCss<'a> for AtRule<'a> { impl<'a> WriteCss<'a> for PageRule<'a> { fn write_css(&self, sink: &mut W) -> Result { sink.write_str("@page")?; - sink.write_trivia_char(' ')?; - if let Some(selectors) = &*self.selectors { - selectors.write_css(sink)?; + if self.selectors.node.children.len() > 0 { + sink.write_char(' ')?; + } + self.selectors.write_css(sink)?; + if self.selectors.node.children.len() > 0 { sink.write_trivia_char(' ')?; } sink.write_char('{')?; @@ -127,7 +129,6 @@ impl<'a> WriteCss<'a> for PageSelector<'a> { fn write_css(&self, sink: &mut W) -> Result { if let Some(page_type) = &self.page_type { sink.write_str(page_type.as_ref())?; - sink.write_char(' ')?; } for pseudo in self.pseudos.iter() { sink.write_char(':')?; diff --git a/tasks/coverage/snapshots/postcss_parser/atrule-decls.json b/tasks/coverage/snapshots/postcss_parser/atrule-decls.json index a3bb5096..ec8ab8d1 100644 --- a/tasks/coverage/snapshots/postcss_parser/atrule-decls.json +++ b/tasks/coverage/snapshots/postcss_parser/atrule-decls.json @@ -1,203 +1,417 @@ { "end": 201, - "rules": [ - { - "end": 37, - "properties": [ - { - "end": 35, - "important": false, - "start": 18, - "type": "Background", - "value": { - "name": "black", - "type": "NamedColor" - } - } - ], - "rules": [], - "selectors": [ - { - "end": 11, - "page_type": null, - "pseudos": [ - "left" - ], - "start": 6, - "type": "PageSelector" - } - ], - "start": 0, - "type": "PageRule" - }, - { - "end": 85, - "name": "media", - "prelude": { - "end": 59, - "start": 45, - "value": [ - { - "end": 59, - "start": 45, - "type": "SimpleBlock", - "value": [ + "node": { + "rules": [ + { + "end": 39, + "node": { + "end": 37, + "node": { + "properties": [ { - "end": 55, - "escaped": false, - "kind": "Ident", - "start": 46, - "value": "min-width" - }, - { - "end": 56, - "escaped": false, - "kind": "Colon", - "start": 55, - "value": null - }, - { - "end": 58, - "escaped": false, - "kind": "Number", - "start": 57, - "value": { - "int": true, - "signed": false, - "value": 0.0 - } + "end": 36, + "node": { + "end": 36, + "node": { + "important": false, + "type": "Background", + "value": { + "end": 36, + "node": { + "type": "Named", + "value": { + "name": "black" + } + }, + "start": 30 + } + }, + "start": 18 + }, + "start": 18 } - ] - } - ] + ], + "rules": [], + "selectors": { + "end": 12, + "node": { + "children": [ + { + "end": 12, + "node": { + "page_type": null, + "pseudos": [ + { + "end": 12, + "node": "left", + "start": 6 + } + ], + "type": "PageSelector" + }, + "start": 6 + } + ], + "type": "PageSelectorList" + }, + "start": 6 + }, + "type": "PageRule" + }, + "start": 0 + }, + "start": 0 }, - "properties": [ - { - "end": 83, - "important": false, - "name": "background", - "start": 66, - "type": "UnknownDeclaration", - "value": { - "end": 83, - "start": 78, - "value": [ - { - "end": 83, - "escaped": false, - "kind": "Ident", - "start": 78, - "value": "white" - } - ] - } - } - ], - "rules": [], - "start": 39, - "type": "UnknownAtRule" - }, - { - "end": 130, - "name": "font-face", - "prelude": null, - "properties": [ - { - "end": 128, - "important": false, - "name": "family-name", - "start": 104, - "type": "UnknownDeclaration", - "value": { - "end": 127, - "start": 117, - "value": [ - { - "end": 127, - "escaped": false, - "kind": "String", - "start": 117, - "value": "A;' /**/" - } - ] - } - } - ], - "rules": [], - "start": 87, - "type": "UnknownAtRule" - }, - { - "end": 163, - "name": "viewport", - "prelude": null, - "properties": [ - { - "end": 161, - "important": false, - "name": "width", - "start": 148, - "type": "UnknownDeclaration", - "value": { - "end": 160, - "start": 155, - "value": [ - { - "end": 160, - "escaped": false, - "kind": "Dimension", - "start": 155, - "value": { - "int": true, - "signed": false, - "unit": "px", - "value": 110.0 - } - } - ] - } - } - ], - "rules": [], - "start": 132, - "type": "UnknownAtRule" - }, - { - "end": 200, - "name": "-ms-viewport", - "prelude": null, - "properties": [ - { - "end": 198, - "important": false, - "name": "width", - "start": 185, - "type": "UnknownDeclaration", - "value": { - "end": 197, - "start": 192, - "value": [ - { - "end": 197, - "escaped": false, - "kind": "Dimension", - "start": 192, - "value": { - "int": true, - "signed": false, - "unit": "px", - "value": 100.0 - } - } - ] - } - } - ], - "rules": [], - "start": 165, - "type": "UnknownAtRule" - } - ], - "start": 0, - "type": "Stylesheet" + { + "end": 201, + "node": { + "end": 201, + "node": { + "name": "media", + "prelude": { + "end": 201, + "node": { + "value": [ + { + "end": 58, + "node": { + "end": 58, + "node": { + "pairwise": "Paren", + "type": "SimpleBlock", + "value": [ + { + "end": 55, + "node": { + "end": 55, + "escaped": false, + "kind": "Ident", + "start": 46, + "value": "min-width" + }, + "start": 46 + }, + { + "end": 56, + "node": { + "end": 56, + "escaped": false, + "kind": "Colon", + "start": 55, + "value": null + }, + "start": 55 + }, + { + "end": 58, + "node": { + "end": 58, + "escaped": false, + "kind": "Number", + "start": 57, + "value": { + "int": true, + "signed": false, + "value": 0.0 + } + }, + "start": 57 + } + ] + }, + "start": 45 + }, + "start": 45 + }, + { + "end": 59, + "node": { + "end": 59, + "escaped": false, + "kind": "RightParen", + "start": 58, + "value": null + }, + "start": 58 + }, + { + "end": 84, + "node": { + "end": 84, + "node": { + "pairwise": "Curly", + "type": "SimpleBlock", + "value": [ + { + "end": 76, + "node": { + "end": 76, + "escaped": false, + "kind": "Ident", + "start": 66, + "value": "background" + }, + "start": 66 + }, + { + "end": 77, + "node": { + "end": 77, + "escaped": false, + "kind": "Colon", + "start": 76, + "value": null + }, + "start": 76 + }, + { + "end": 83, + "node": { + "end": 83, + "escaped": false, + "kind": "Ident", + "start": 78, + "value": "white" + }, + "start": 78 + } + ] + }, + "start": 60 + }, + "start": 60 + }, + { + "end": 97, + "node": { + "end": 97, + "escaped": false, + "kind": "AtKeyword", + "start": 87, + "value": "font-face" + }, + "start": 87 + }, + { + "end": 129, + "node": { + "end": 129, + "node": { + "pairwise": "Curly", + "type": "SimpleBlock", + "value": [ + { + "end": 115, + "node": { + "end": 115, + "escaped": false, + "kind": "Ident", + "start": 104, + "value": "family-name" + }, + "start": 104 + }, + { + "end": 116, + "node": { + "end": 116, + "escaped": false, + "kind": "Colon", + "start": 115, + "value": null + }, + "start": 115 + }, + { + "end": 127, + "node": { + "end": 127, + "escaped": false, + "kind": "String", + "start": 117, + "value": "A;' /**/" + }, + "start": 117 + }, + { + "end": 128, + "node": { + "end": 128, + "escaped": false, + "kind": "Semicolon", + "start": 127, + "value": null + }, + "start": 127 + } + ] + }, + "start": 98 + }, + "start": 98 + }, + { + "end": 141, + "node": { + "end": 141, + "escaped": false, + "kind": "AtKeyword", + "start": 132, + "value": "viewport" + }, + "start": 132 + }, + { + "end": 162, + "node": { + "end": 162, + "node": { + "pairwise": "Curly", + "type": "SimpleBlock", + "value": [ + { + "end": 153, + "node": { + "end": 153, + "escaped": false, + "kind": "Ident", + "start": 148, + "value": "width" + }, + "start": 148 + }, + { + "end": 154, + "node": { + "end": 154, + "escaped": false, + "kind": "Colon", + "start": 153, + "value": null + }, + "start": 153 + }, + { + "end": 160, + "node": { + "end": 160, + "escaped": false, + "kind": "Dimension", + "start": 155, + "value": { + "int": true, + "signed": false, + "unit": "px", + "value": 110.0 + } + }, + "start": 155 + }, + { + "end": 161, + "node": { + "end": 161, + "escaped": false, + "kind": "Semicolon", + "start": 160, + "value": null + }, + "start": 160 + } + ] + }, + "start": 142 + }, + "start": 142 + }, + { + "end": 178, + "node": { + "end": 178, + "escaped": false, + "kind": "AtKeyword", + "start": 165, + "value": "-ms-viewport" + }, + "start": 165 + }, + { + "end": 199, + "node": { + "end": 199, + "node": { + "pairwise": "Curly", + "type": "SimpleBlock", + "value": [ + { + "end": 190, + "node": { + "end": 190, + "escaped": false, + "kind": "Ident", + "start": 185, + "value": "width" + }, + "start": 185 + }, + { + "end": 191, + "node": { + "end": 191, + "escaped": false, + "kind": "Colon", + "start": 190, + "value": null + }, + "start": 190 + }, + { + "end": 197, + "node": { + "end": 197, + "escaped": false, + "kind": "Dimension", + "start": 192, + "value": { + "int": true, + "signed": false, + "unit": "px", + "value": 100.0 + } + }, + "start": 192 + }, + { + "end": 198, + "node": { + "end": 198, + "escaped": false, + "kind": "Semicolon", + "start": 197, + "value": null + }, + "start": 197 + } + ] + }, + "start": 179 + }, + "start": 179 + } + ] + }, + "start": 45 + }, + "properties": [], + "rules": [], + "type": "UnknownAtRule" + }, + "start": 39 + }, + "start": 39 + } + ], + "type": "Stylesheet" + }, + "start": 0 } \ No newline at end of file diff --git a/tasks/coverage/snapshots/postcss_parser/atrule-no-params.json b/tasks/coverage/snapshots/postcss_parser/atrule-no-params.json index b699a182..ace073e7 100644 --- a/tasks/coverage/snapshots/postcss_parser/atrule-no-params.json +++ b/tasks/coverage/snapshots/postcss_parser/atrule-no-params.json @@ -9,7 +9,14 @@ "node": { "properties": [], "rules": [], - "selectors": null, + "selectors": { + "end": 0, + "node": { + "children": [], + "type": "PageSelectorList" + }, + "start": 0 + }, "type": "PageRule" }, "start": 0