diff --git a/Cargo.lock b/Cargo.lock index 9749a821..ac5b9fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.1.2" @@ -71,6 +86,30 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -116,6 +155,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -351,6 +399,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glob" version = "0.3.1" @@ -423,6 +477,7 @@ dependencies = [ "hdx_parser", "hdx_syntax", "hdx_writer", + "miette", "oxc_allocator", "serde", "serde_json", @@ -700,6 +755,8 @@ version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "337e1043bbc086dac9d9674983bef52ac991ce150e09b5b8e35c5a73dd83f66c" dependencies = [ + "backtrace", + "backtrace-ext", "miette-derive", "owo-colors", "supports-color", @@ -722,6 +779,15 @@ dependencies = [ "syn", ] +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -737,6 +803,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -974,6 +1049,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.38.31" diff --git a/crates/hdx/Cargo.toml b/crates/hdx/Cargo.toml index b2094c00..c9f9f593 100644 --- a/crates/hdx/Cargo.toml +++ b/crates/hdx/Cargo.toml @@ -19,6 +19,7 @@ hdx_atom = { workspace = true } hdx_derive = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } +miette = { workspace = true } # Use OXC Allocator until https://github.com/fitzgen/bumpalo/pull/210 is resolved oxc_allocator = { workspace = true } @@ -28,5 +29,6 @@ serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } [features] -default = [] -serde = ["dep:serde", "dep:serde_json", "hdx_lexer/serde"] +default = ["fancy"] +serde = ["dep:serde", "dep:serde_json", "hdx_lexer/serde"] +fancy = ["hdx_ast/fancy", "hdx_parser/fancy", "miette/fancy"] diff --git a/crates/hdx/src/main.rs b/crates/hdx/src/main.rs index 08f4a9eb..bd4211e4 100644 --- a/crates/hdx/src/main.rs +++ b/crates/hdx/src/main.rs @@ -1,6 +1,7 @@ use clap::Parser; use hdx_ast::css::StyleSheet; use hdx_writer::{BaseCssWriter, WriteCss}; +use miette::{NamedSource, GraphicalReportHandler, GraphicalTheme}; use oxc_allocator::Allocator; #[derive(Debug, Parser)] @@ -25,14 +26,11 @@ fn main() { todo!("Can't handle multiple files yet") } - let source_text = std::fs::read_to_string(args.input.first().unwrap()).unwrap(); + let file_name = args.input.first().unwrap(); + let source_text = std::fs::read_to_string(file_name).unwrap(); let allocator = Allocator::default(); - let result = hdx_parser::Parser::new( - &allocator, - source_text.as_str(), - hdx_parser::Features::default(), - ) - .parse_with::(); + let result = hdx_parser::Parser::new(&allocator, source_text.as_str(), hdx_parser::Features::default()) + .parse_with::(); { let start = std::time::Instant::now(); let mut str = String::new(); @@ -46,7 +44,14 @@ fn main() { eprintln!("Slurped up CSS in {:?}! Neat!", start.elapsed()); } } else { - eprintln!("{:?}", result.errors); + let handler = GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor()); + for err in result.errors { + let mut report = String::new(); + let named = NamedSource::new(file_name, source_text.clone()); + let err = err.with_source_code(named); + handler.render_report(&mut report, err.as_ref()).unwrap(); + println!("{}", report); + } } } } diff --git a/crates/hdx_ast/Cargo.toml b/crates/hdx_ast/Cargo.toml index 71234a6e..574aca37 100644 --- a/crates/hdx_ast/Cargo.toml +++ b/crates/hdx_ast/Cargo.toml @@ -35,3 +35,4 @@ serde_json = { workspace = true } [features] default = [] serde = ["dep:serde", "dep:serde_json", "hdx_lexer/serde"] +fancy = ["miette/fancy-no-backtrace"] diff --git a/crates/hdx_ast/src/css/properties.rs b/crates/hdx_ast/src/css/properties.rs index d4d1cc98..0312580c 100644 --- a/crates/hdx_ast/src/css/properties.rs +++ b/crates/hdx_ast/src/css/properties.rs @@ -32,6 +32,29 @@ impl<'a> Parse<'a> for Custom<'a> { } } +#[derive(Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] +pub struct Computed<'a> { + pub value: Box<'a, Spanned>>, + // pub value_like: Spanned>, +} + +impl<'a> WriteCss<'a> for Computed<'a> { + fn write_css(&self, sink: &mut W) -> WriterResult { + self.value.write_css(sink) + } +} + +impl<'a> Parse<'a> for Computed<'a> { + fn parse(parser: &mut Parser<'a>) -> ParserResult> { + let span = parser.span(); + parser.set(State::StopOnSemicolon); + let value = ComponentValues::parse(parser)?; + parser.unset(State::StopOnSemicolon); + Ok(Self { value: parser.boxup(value) }.spanned(span.end(parser.pos()))) + } +} + #[derive(Debug, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] pub struct Unknown<'a> { @@ -75,6 +98,37 @@ impl<'a> WriteCss<'a> for StyleProperty<'a> { } } +#[inline] +fn is_computed_token(token: Token) -> bool { + match token { + Token::Function(atom) => match atom.to_ascii_lowercase() { + atom!("var") + | atom!("calc") + | atom!("min") + | atom!("max") + | atom!("clamp") + | atom!("round") + | atom!("mod") + | atom!("rem") + | atom!("sin") + | atom!("cos") + | atom!("tan") + | atom!("asin") + | atom!("atan") + | atom!("atan2") + | atom!("pow") + | atom!("sqrt") + | atom!("hypot") + | atom!("log") + | atom!("exp") + | atom!("abs") + | atom!("sign") => true, + _ => false, + }, + _ => false, + } +} + macro_rules! properties { ( $( $name: ident$(<$a: lifetime>)?: $atom: pat, @@ -88,6 +142,7 @@ macro_rules! properties { Revert, RevertLayer, Custom(Box<'a, Spanned>>), + Computed(Box<'a, Spanned>>), Unknown(Box<'a, Spanned>>), $( $name(Box<'a, Spanned)?>>), @@ -104,6 +159,7 @@ macro_rules! properties { Self::RevertLayer => sink.write_str("revert-layer")?, Self::Custom(v) => v.write_css(sink)?, Self::Unknown(v) => v.write_css(sink)?, + Self::Computed(v) => v.write_css(sink)?, $( Self::$name(v) => v.write_css(sink)?, )+ @@ -147,13 +203,36 @@ macro_rules! properties { StyleValue::RevertLayer }, _ => { - let parsed = values::$name::parse(parser)?; - StyleValue::$name(parser.boxup(parsed)) + let checkpoint = parser.checkpoint(); + if let Ok(value) = values::$name::parse(parser) { + StyleValue::$name(parser.boxup(value)) + } else if is_computed_token(parser.cur()) { + parser.rewind(checkpoint); + let value = Computed::parse(parser)?; + StyleValue::Computed(parser.boxup(value)) + } else { + parser.rewind(checkpoint); + unexpected!(parser) + } } }, _ => { - let parsed = values::$name::parse(parser)?; - StyleValue::$name(parser.boxup(parsed)) + if is_computed_token(parser.cur()) { + let value = Computed::parse(parser)?; + StyleValue::Computed(parser.boxup(value)) + } else { + let checkpoint = parser.checkpoint(); + if let Ok(value) = values::$name::parse(parser) { + StyleValue::$name(parser.boxup(value)) + } else if is_computed_token(parser.cur()) { + parser.rewind(checkpoint); + let value = Computed::parse(parser)?; + StyleValue::Computed(parser.boxup(value)) + } else { + parser.rewind(checkpoint); + unexpected!(parser) + } + } } }; (name, value) @@ -1276,10 +1355,10 @@ properties! { Zoom: atom!("zoom"), // Webkit NonStandards - WebkitTextSizeAdjust: atom!("-webkit-text-size-adjust"), - WebkitTextDecoration: atom!("-webkit-text-decoration"), - WebkitTapHighlightColor: atom!("-webkit-tap-highlight-color"), - WebkitTextDecorationSkipInk: atom!("-webkit-text-decoration-skip-ink"), + // WebkitTextSizeAdjust: atom!("-webkit-text-size-adjust"), + // WebkitTextDecoration: atom!("-webkit-text-decoration"), + // WebkitTapHighlightColor: atom!("-webkit-tap-highlight-color"), + // WebkitTextDecorationSkipInk: atom!("-webkit-text-decoration-skip-ink"), } #[cfg(test)] @@ -1300,5 +1379,6 @@ mod tests { fn test_writes() { let allocator = Allocator::default(); test_write::(&allocator, "width: 1px", "width:1px"); + test_write::(&allocator, "width: min(1px, 2px)", "width:min(1px, 2px)"); } } diff --git a/crates/hdx_ast/src/css/selector/mod.rs b/crates/hdx_ast/src/css/selector/mod.rs index f1c8855c..6dbed131 100644 --- a/crates/hdx_ast/src/css/selector/mod.rs +++ b/crates/hdx_ast/src/css/selector/mod.rs @@ -61,6 +61,7 @@ pub struct Selector<'a> { impl<'a> Parse<'a> for Selector<'a> { fn parse(parser: &mut Parser<'a>) -> ParserResult> { + discard!(parser, Token::Whitespace); let span = parser.span(); let mut components: Vec<'a, Spanned> = parser.new_vec(); loop { diff --git a/crates/hdx_ast/src/css/values/box/margin.rs b/crates/hdx_ast/src/css/values/box/margin.rs deleted file mode 100644 index 58409505..00000000 --- a/crates/hdx_ast/src/css/values/box/margin.rs +++ /dev/null @@ -1 +0,0 @@ -pub type Margin = super::super::Todo; diff --git a/crates/hdx_ast/src/css/values/box/margin_bottom.rs b/crates/hdx_ast/src/css/values/box/margin_bottom.rs deleted file mode 100644 index 91fb6567..00000000 --- a/crates/hdx_ast/src/css/values/box/margin_bottom.rs +++ /dev/null @@ -1 +0,0 @@ -pub type MarginBottom = super::super::Todo; diff --git a/crates/hdx_ast/src/css/values/box/margin_left.rs b/crates/hdx_ast/src/css/values/box/margin_left.rs deleted file mode 100644 index 1f08b68f..00000000 --- a/crates/hdx_ast/src/css/values/box/margin_left.rs +++ /dev/null @@ -1 +0,0 @@ -pub type MarginLeft = super::super::Todo; diff --git a/crates/hdx_ast/src/css/values/box/margin_right.rs b/crates/hdx_ast/src/css/values/box/margin_right.rs deleted file mode 100644 index 3c880a50..00000000 --- a/crates/hdx_ast/src/css/values/box/margin_right.rs +++ /dev/null @@ -1 +0,0 @@ -pub type MarginRight = super::super::Todo; diff --git a/crates/hdx_ast/src/css/values/box/margin_top.rs b/crates/hdx_ast/src/css/values/box/margin_top.rs deleted file mode 100644 index 363780ec..00000000 --- a/crates/hdx_ast/src/css/values/box/margin_top.rs +++ /dev/null @@ -1 +0,0 @@ -pub type MarginTop = super::super::Todo; diff --git a/crates/hdx_ast/src/css/values/box/margin_trim.rs b/crates/hdx_ast/src/css/values/box/margin_trim.rs index a3ad5439..d591134f 100644 --- a/crates/hdx_ast/src/css/values/box/margin_trim.rs +++ b/crates/hdx_ast/src/css/values/box/margin_trim.rs @@ -5,13 +5,14 @@ use hdx_writer::{CssWriter, Result as WriterResult, WriteCss}; #[cfg(feature = "serde")] use serde::Serialize; -use crate::{bitmask, Atomizable}; +use crate::{bitmask, Atomizable, Value}; // https://drafts.csswg.org/css-box-4/#propdef-margin-trim -#[derive(Atomizable)] +#[derive(Atomizable, Default)] #[bitmask(u8)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum MarginTrim { + #[default] None, Block, Inline, @@ -21,6 +22,8 @@ pub enum MarginTrim { InlineEnd, } +impl Value for MarginTrim {} + impl<'a> Parse<'a> for MarginTrim { fn parse(parser: &mut Parser<'a>) -> ParserResult> { let span = parser.span(); diff --git a/crates/hdx_ast/src/css/values/box/mod.rs b/crates/hdx_ast/src/css/values/box/mod.rs index fb9610b4..4643b357 100644 --- a/crates/hdx_ast/src/css/values/box/mod.rs +++ b/crates/hdx_ast/src/css/values/box/mod.rs @@ -1,22 +1,114 @@ +#[cfg(feature = "serde")] +use serde::Serialize; + mod margin_trim; pub use margin_trim::*; use super::Todo; -use crate::macros::{length_percentage_property, positive_length_percentage_property}; +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; // https://drafts.csswg.org/css-box-4 // https://drafts.csswg.org/css-box-4/#padding-physical pub type Padding = Todo; -positive_length_percentage_property!(PaddingTop); -positive_length_percentage_property!(PaddingRight); -positive_length_percentage_property!(PaddingBottom); -positive_length_percentage_property!(PaddingLeft); + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub struct PaddingTop(#[parsable(FromToken)] pub LengthPercentage); + +impl Value for PaddingTop {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub struct PaddingRight(#[parsable(FromToken)] pub LengthPercentage); + +impl Value for PaddingRight {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub struct PaddingBottom(#[parsable(FromToken)] pub LengthPercentage); + +impl Value for PaddingBottom {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub struct PaddingLeft(#[parsable(FromToken)] pub LengthPercentage); + +impl Value for PaddingLeft {} // https://drafts.csswg.org/css-box-4/#margin-physical pub type Margin = Todo; -length_percentage_property!(MarginTop); -length_percentage_property!(MarginRight); -length_percentage_property!(MarginBottom); -length_percentage_property!(MarginLeft); + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum MarginTop { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for MarginTop {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum MarginRight { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for MarginRight {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum MarginBottom { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for MarginBottom {} + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum MarginLeft { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for MarginLeft {} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "1px", "1px"); + test_write::(&allocator, "100%", "100%"); + test_write::(&allocator, "auto", "auto"); + } +} diff --git a/crates/hdx_ast/src/css/values/box/padding_bottom.rs b/crates/hdx_ast/src/css/values/box/padding_bottom.rs deleted file mode 100644 index 4faeb5ee..00000000 --- a/crates/hdx_ast/src/css/values/box/padding_bottom.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::css::values::units::{LengthPercentage, Positive}; - -// https://drafts.csswg.org/css-box-4/#padding-physical -pub type PaddingBottom = Positive; diff --git a/crates/hdx_ast/src/css/values/box/padding_left.rs b/crates/hdx_ast/src/css/values/box/padding_left.rs deleted file mode 100644 index ea41681e..00000000 --- a/crates/hdx_ast/src/css/values/box/padding_left.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::css::values::units::{LengthPercentage, Positive}; - -// https://drafts.csswg.org/css-box-4/#padding-physical -pub type PaddingLeft = Positive; diff --git a/crates/hdx_ast/src/css/values/box/padding_right.rs b/crates/hdx_ast/src/css/values/box/padding_right.rs deleted file mode 100644 index 25f824ed..00000000 --- a/crates/hdx_ast/src/css/values/box/padding_right.rs +++ /dev/null @@ -1,30 +0,0 @@ -use hdx_lexer::Token; -use hdx_parser::{ - diagnostics::{NumberNotNegative, UnexpectedDimension}, - unexpected, Parse, -}; - -use crate::css::values::units::LengthPercentage; - -// https://drafts.csswg.org/css-box-4/#padding-physical -pub type PaddingRight = LengthPercentage; - -impl<'a> Parse<'a> for PaddingRight { - fn parse(parser: &mut hdx_parser::Parser<'a>) -> miette::Result> { - let span = parser.span(); - match parser.cur() { - Token::Number(n, _) if n == 0.0 => Ok(Self::Zero.spanned(span)), - Token::Dimension(n, unit, _) if n == 0.0 => { - if n < 0 { - Err(NumberNotNegative(n, span))? - } - if let Some(val) = Self::new(n.into(), unit) { - Ok(val.spanned(span)) - } else { - Err(UnexpectedDimension(unit, span))? - } - } - token => unexpected!(parser, token), - } - } -} diff --git a/crates/hdx_ast/src/css/values/box/padding_top.rs b/crates/hdx_ast/src/css/values/box/padding_top.rs deleted file mode 100644 index 2a242142..00000000 --- a/crates/hdx_ast/src/css/values/box/padding_top.rs +++ /dev/null @@ -1,30 +0,0 @@ -use hdx_lexer::Token; -use hdx_parser::{ - diagnostics::{NumberNotNegative, UnexpectedDimension}, - unexpected, Parse, -}; - -use crate::css::values::units::LengthPercentage; - -// https://drafts.csswg.org/css-box-4/#padding-physical -pub struct PaddingTop(pub LengthPercentage); - -impl<'a> Parse<'a> for PaddingTop { - fn parse(parser: &mut hdx_parser::Parser<'a>) -> miette::Result> { - let span = parser.span(); - match parser.cur() { - Token::Number(n, _) if n == 0.0 => Ok(Self::Zero.spanned(span)), - Token::Dimension(n, unit, _) if n == 0.0 => { - if n < 0 { - Err(NumberNotNegative(n, span))? - } - if let Some(val) = Self::new(n.into(), unit) { - Ok(val.spanned(span)) - } else { - Err(UnexpectedDimension(unit, span))? - } - } - token => unexpected!(parser, token), - } - } -} diff --git a/crates/hdx_ast/src/css/values/break/break_before.rs b/crates/hdx_ast/src/css/values/break/break_before.rs index 11f54e8c..f65d91c4 100644 --- a/crates/hdx_ast/src/css/values/break/break_before.rs +++ b/crates/hdx_ast/src/css/values/break/break_before.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Writable, Value}; // https://drafts.csswg.org/css-break-4/#propdef-break-before #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -24,6 +24,8 @@ pub enum BreakBefore { Region, // atom!("region") } +impl Value for BreakBefore {} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/break/margin_break.rs b/crates/hdx_ast/src/css/values/break/margin_break.rs index 464f5f93..3102c2b9 100644 --- a/crates/hdx_ast/src/css/values/break/margin_break.rs +++ b/crates/hdx_ast/src/css/values/break/margin_break.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Writable, Value}; // https://drafts.csswg.org/css-break-4/#propdef-margin-break #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -12,3 +12,5 @@ pub enum MarginBreak { Keep, // atom!("keep") Discard, // atom!("discard") } + +impl Value for MarginBreak {} diff --git a/crates/hdx_ast/src/css/values/content/content.rs b/crates/hdx_ast/src/css/values/content/content.rs index 7bc4c42c..6812a858 100644 --- a/crates/hdx_ast/src/css/values/content/content.rs +++ b/crates/hdx_ast/src/css/values/content/content.rs @@ -1 +1,85 @@ -pub type Content = super::super::Todo; +use hdx_atom::{atom, Atom}; +use hdx_lexer::Token; +use hdx_parser::{unexpected, unexpected_ident, Parse}; + +use hdx_writer::WriteCss; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{Parsable, Value, Vec, Writable}; + +#[derive(Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Content { + #[default] + Normal, + None, + // Image(), // TODO: Implement image + String(Atom), + // CounterFunction(), // TODO: Implement counter() + // CountersFunction(), // TODO: Implement counters() + // ContentFunction(), // TODO: Implement content() + // AttrFunction(), // TODO: Implement attr() +} + +impl Value for Content {} + +impl<'a> Parse<'a> for Content { + fn parse(parser: &mut hdx_parser::Parser<'a>) -> miette::Result> { + let span = parser.span(); + let value = match parser.cur() { + Token::Ident(atom) => match atom.to_ascii_lowercase() { + atom!("normal") => { + parser.advance(); + Self::Normal + } + atom!("none") => { + parser.advance(); + Self::None + } + atom => unexpected_ident!(parser, atom), + }, + Token::String(atom) => { + parser.advance(); + Self::String(atom) + } + token => unexpected!(parser, token), + }; + Ok(value.spanned(span.end(parser.pos()))) + } +} + +impl<'a> WriteCss<'a> for Content { + fn write_css(&self, sink: &mut W) -> hdx_writer::Result { + match self { + Self::None => atom!("none").write_css(sink), + Self::Normal => atom!("normal").write_css(sink), + Self::String(str) => { + sink.write_char('"')?; + sink.write_str(str)?; + sink.write_char('"') + }, + } + } +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 16); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "none", "none"); + test_write::(&allocator, "'foo'", "\"foo\""); + } +} diff --git a/crates/hdx_ast/src/css/values/css2/z_index.rs b/crates/hdx_ast/src/css/values/css2/z_index.rs index 8407b6b6..8755ca89 100644 --- a/crates/hdx_ast/src/css/values/css2/z_index.rs +++ b/crates/hdx_ast/src/css/values/css2/z_index.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Parsable, Writable, css::values::units::CSSFloat}; +use crate::{css::values::units::CSSFloat, Parsable, Value, Writable}; #[derive(Parsable, Writable, Default, Debug, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", content = "value"))] @@ -12,13 +12,25 @@ pub enum ZIndex { Integer(CSSFloat), } +impl Value for ZIndex {} + #[cfg(test)] mod tests { + use oxc_allocator::Allocator; use super::*; + use crate::test_helpers::test_write; #[test] fn size_test() { - assert_eq!(::std::mem::size_of::(), 8); + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "auto", "auto"); + test_write::(&allocator, "999", "999"); } } diff --git a/crates/hdx_ast/src/css/values/display/display.rs b/crates/hdx_ast/src/css/values/display/display.rs index 6bba9f12..c5148366 100644 --- a/crates/hdx_ast/src/css/values/display/display.rs +++ b/crates/hdx_ast/src/css/values/display/display.rs @@ -5,7 +5,7 @@ use hdx_writer::{CssWriter, Result as WriterResult, WriteCss}; #[cfg(feature = "serde")] use serde::Serialize; -use crate::bitmask; +use crate::{bitmask, Value}; // https://drafts.csswg.org/css-display-4/#propdef-display #[derive(Default)] @@ -91,6 +91,8 @@ pub enum Display { Table = 0b0000_0110, } +impl Value for Display {} + impl Display { #[inline] fn outside_bits(&self) -> Self { diff --git a/crates/hdx_ast/src/css/values/display/visibility.rs b/crates/hdx_ast/src/css/values/display/visibility.rs index 0a4e5508..d1ba07e4 100644 --- a/crates/hdx_ast/src/css/values/display/visibility.rs +++ b/crates/hdx_ast/src/css/values/display/visibility.rs @@ -1 +1,39 @@ -pub type Visibility = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Visibility { + #[default] + Visible, // atom!("visible"), + Hidden, // atom!("hidden"), + Collapse, // atom!("collapse"), +} + +impl Value for Visibility { + fn inherits() -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 1); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "visible", "visible"); + } +} diff --git a/crates/hdx_ast/src/css/values/fonts/font_size.rs b/crates/hdx_ast/src/css/values/fonts/font_size.rs index 784d5a3d..c020cefb 100644 --- a/crates/hdx_ast/src/css/values/fonts/font_size.rs +++ b/crates/hdx_ast/src/css/values/fonts/font_size.rs @@ -1 +1,48 @@ -pub type FontSize = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum FontSize { + XxSmall, // atom!("xx-small") + XSmall, // atom!("x-small") + Small, // atom!("small") + #[default] + Medium, // atom!("medium") + Large, // atom!("large") + XLarge, // atom!("x-large") + XxLarge, // atom!("xx-large") + Larger, // atom!("larger") + Smaller, // atom!("smaller") + #[parsable(DimensionOrZero, FromToken, Check::Positive)] + LengthPercentage(LengthPercentage) +} + +impl Value for FontSize { + fn inherits() -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "10px", "10px"); + test_write::(&allocator, "xx-large", "xx-large"); + } +} diff --git a/crates/hdx_ast/src/css/values/inline/alignment_baseline.rs b/crates/hdx_ast/src/css/values/inline/alignment_baseline.rs index 3afae93b..2e9c95cc 100644 --- a/crates/hdx_ast/src/css/values/inline/alignment_baseline.rs +++ b/crates/hdx_ast/src/css/values/inline/alignment_baseline.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Value, Writable}; // https://drafts.csswg.org/css-inline/#propdef-alignment-baseline #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -18,6 +18,8 @@ pub enum AlignmentBaseline { TextTop, // atom!("text-top") } +impl Value for AlignmentBaseline {} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/inline/dominant_baseline.rs b/crates/hdx_ast/src/css/values/inline/dominant_baseline.rs index 49e6a288..cebdf739 100644 --- a/crates/hdx_ast/src/css/values/inline/dominant_baseline.rs +++ b/crates/hdx_ast/src/css/values/inline/dominant_baseline.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Value, Writable}; // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -19,6 +19,12 @@ pub enum DominantBaseline { TextTop, // atom!("text-top") } +impl Value for DominantBaseline { + fn inherits() -> bool { + true + } +} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/inline/line_height.rs b/crates/hdx_ast/src/css/values/inline/line_height.rs index 350755d8..c02523e4 100644 --- a/crates/hdx_ast/src/css/values/inline/line_height.rs +++ b/crates/hdx_ast/src/css/values/inline/line_height.rs @@ -1 +1,46 @@ -pub type LineHeight = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{ + css::values::units::{CSSFloat, LengthPercentage}, + Parsable, Value, Writable, +}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum LineHeight { + #[default] + Normal, // atom!("medium") + #[parsable(Number, Check::Positive)] + Number(CSSFloat), + #[parsable(Dimension, FromToken, Check::Positive)] + LengthPercentage(LengthPercentage), +} + +impl Value for LineHeight { + fn inherits() -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "10px", "10px"); + test_write::(&allocator, "1.25", "1.25"); + test_write::(&allocator, "normal", "normal"); + } +} diff --git a/crates/hdx_ast/src/css/values/non_standard/zoom.rs b/crates/hdx_ast/src/css/values/non_standard/zoom.rs index 261c3e36..a657beda 100644 --- a/crates/hdx_ast/src/css/values/non_standard/zoom.rs +++ b/crates/hdx_ast/src/css/values/non_standard/zoom.rs @@ -1,10 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{ - css::values::units::{CSSFloat}, - Parsable, Writable, -}; +use crate::{css::values::units::CSSFloat, Parsable, Writable}; #[derive(Parsable, Writable, Default, Debug, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", content = "value"))] diff --git a/crates/hdx_ast/src/css/values/overflow/overflow.rs b/crates/hdx_ast/src/css/values/overflow/overflow.rs index 9e166d73..4fa3ba43 100644 --- a/crates/hdx_ast/src/css/values/overflow/overflow.rs +++ b/crates/hdx_ast/src/css/values/overflow/overflow.rs @@ -1 +1 @@ -pub type Overflow = super::super::Todo; +pub type Overflow = super::OverflowBlock; // TODO: Fix this to be a shorthand diff --git a/crates/hdx_ast/src/css/values/overflow/overflow_block.rs b/crates/hdx_ast/src/css/values/overflow/overflow_block.rs index 69964be0..f3d63646 100644 --- a/crates/hdx_ast/src/css/values/overflow/overflow_block.rs +++ b/crates/hdx_ast/src/css/values/overflow/overflow_block.rs @@ -17,11 +17,20 @@ pub enum OverflowBlock { #[cfg(test)] mod tests { + use oxc_allocator::Allocator; use super::*; + use crate::test_helpers::test_write; #[test] fn size_test() { - assert_eq!(::std::mem::size_of::(), 1); + use std::mem::size_of; + assert_eq!(size_of::(), 1); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "clip", "clip"); } } diff --git a/crates/hdx_ast/src/css/values/page_floats/clear.rs b/crates/hdx_ast/src/css/values/page_floats/clear.rs index ebf7d1e4..41a0bd99 100644 --- a/crates/hdx_ast/src/css/values/page_floats/clear.rs +++ b/crates/hdx_ast/src/css/values/page_floats/clear.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Writable, Value}; // https://drafts.csswg.org/css-page-floats-3/#propdef-clear #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -22,6 +22,8 @@ pub enum Clear { None, // atom!("none") } +impl Value for Clear {} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/page_floats/float.rs b/crates/hdx_ast/src/css/values/page_floats/float.rs index b50e615a..09c7eb6e 100644 --- a/crates/hdx_ast/src/css/values/page_floats/float.rs +++ b/crates/hdx_ast/src/css/values/page_floats/float.rs @@ -1 +1,223 @@ -pub type Float = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use hdx_atom::atom; +use hdx_lexer::Token; +use hdx_parser::{diagnostics, expect, unexpected, unexpected_ident, FromToken, Parse}; +use hdx_writer::WriteCss; + +use crate::{css::values::units::Length, Atomizable, Value}; + +#[derive(Debug, PartialEq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Float { + #[default] + None, + Left, + Right, + Top, + Bottom, + BlockStart, + BlockEnd, + InlineStart, + InlineEnd, + SnapBlock, + SnapBlockFunction(Length, Option), + SnapInline, + SnapInlineFunction(Length, Option), +} + +impl Value for Float {} + +impl<'a> Parse<'a> for Float { + fn parse(parser: &mut hdx_parser::Parser<'a>) -> miette::Result> { + let span = parser.span(); + let value = match parser.cur() { + Token::Ident(atom) => match atom.to_ascii_lowercase() { + atom!("none") => { + parser.advance(); + Float::None + } + atom!("left") => { + parser.advance(); + Float::Left + } + atom!("right") => { + parser.advance(); + Float::Right + } + atom!("top") => { + parser.advance(); + Float::Top + } + atom!("bottom") => { + parser.advance(); + Float::Bottom + } + atom!("block-start") => { + parser.advance(); + Float::BlockStart + } + atom!("block-end") => { + parser.advance(); + Float::BlockEnd + } + atom!("inline-start") => { + parser.advance(); + Float::InlineStart + } + atom!("inline-end") => { + parser.advance(); + Float::InlineEnd + } + atom!("snap-block") => { + parser.advance(); + Float::SnapBlock + } + atom!("snap-inline") => { + parser.advance(); + Float::SnapInline + } + atom => unexpected_ident!(parser, atom), + }, + Token::Function(atom) => match atom.to_ascii_lowercase() { + atom!("snap-block") => { + parser.advance(); + let length = + if let Some(length) = Length::from_token(parser.cur()) { length } else { unexpected!(parser) }; + parser.advance(); + let dir = match parser.cur() { + Token::Comma => { + parser.advance(); + match parser.cur() { + Token::Ident(atom) => { + if let Some(dir) = SnapBlockDirection::from_atom(atom.to_ascii_lowercase()) { + parser.advance(); + Some(dir) + } else { + unexpected_ident!(parser, atom) + } + } + token => unexpected!(parser, token), + } + } + Token::RightParen => None, + token => unexpected!(parser, token), + }; + expect!(parser, Token::RightParen); + parser.advance(); + Float::SnapBlockFunction(length, dir) + } + atom!("snap-inline") => { + parser.advance(); + let length = + if let Some(length) = Length::from_token(parser.cur()) { length } else { unexpected!(parser) }; + parser.advance(); + let dir = match parser.cur() { + Token::Comma => { + parser.advance(); + match parser.cur() { + Token::Ident(atom) => { + if let Some(dir) = SnapInlineDirection::from_atom(atom.to_ascii_lowercase()) { + parser.advance(); + Some(dir) + } else { + unexpected_ident!(parser, atom) + } + } + token => unexpected!(parser, token), + } + } + Token::RightParen => None, + token => unexpected!(parser, token), + }; + expect!(parser, Token::RightParen); + parser.advance(); + Float::SnapInlineFunction(length, dir) + } + atom => Err(diagnostics::UnexpectedFunction(atom, parser.span()))?, + }, + token => unexpected!(parser, token), + }; + Ok(value.spanned(span.end(parser.pos()))) + } +} + +impl<'a> WriteCss<'a> for Float { + fn write_css(&self, sink: &mut W) -> hdx_writer::Result { + match self { + Self::None => sink.write_str("none"), + Self::Left => sink.write_str("left"), + Self::Right => sink.write_str("right"), + Self::Top => sink.write_str("top"), + Self::Bottom => sink.write_str("bottom"), + Self::BlockStart => sink.write_str("block-start"), + Self::BlockEnd => sink.write_str("block-end"), + Self::InlineStart => sink.write_str("inline-start"), + Self::InlineEnd => sink.write_str("inline-end"), + Self::SnapBlock => sink.write_str("snap-block"), + Self::SnapBlockFunction(len, dir) => { + sink.write_str("snap-block(")?; + len.write_css(sink)?; + if let Some(direction) = dir { + sink.write_char(',')?; + sink.write_trivia_char(' ')?; + direction.to_atom().write_css(sink)?; + } + sink.write_char(')') + } + Self::SnapInline => sink.write_str("snap-inline"), + Self::SnapInlineFunction(len, dir) => { + sink.write_str("snap-inline(")?; + len.write_css(sink)?; + if let Some(direction) = dir { + sink.write_char(',')?; + sink.write_trivia_char(' ')?; + direction.to_atom().write_css(sink)?; + } + sink.write_char(')') + } + } + } +} + +#[derive(Atomizable, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum SnapBlockDirection { + Start, // atom!("start") + End, // atom!("end") + Near, // atom!("near") +} + +#[derive(Atomizable, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum SnapInlineDirection { + Left, // atom!("left") + Right, // atom!("right") + Near, // atom!("near") +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 12); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "none", "none"); + test_write::(&allocator, "left", "left"); + test_write::(&allocator, "right", "right"); + test_write::(&allocator, "block-end", "block-end"); + test_write::(&allocator, "snap-inline(20rem, left)", "snap-inline(20rem,left)"); + test_write::(&allocator, "snap-block(4px, end)", "snap-block(4px,end)"); + } +} diff --git a/crates/hdx_ast/src/css/values/position/bottom.rs b/crates/hdx_ast/src/css/values/position/bottom.rs index 4d0fe56d..2af4dd72 100644 --- a/crates/hdx_ast/src/css/values/position/bottom.rs +++ b/crates/hdx_ast/src/css/values/position/bottom.rs @@ -1 +1,36 @@ -pub type Bottom = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Bottom { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for Bottom {} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "-10px", "-10px"); + test_write::(&allocator, "auto", "auto"); + } +} diff --git a/crates/hdx_ast/src/css/values/position/left.rs b/crates/hdx_ast/src/css/values/position/left.rs index 0453fb4b..f4065788 100644 --- a/crates/hdx_ast/src/css/values/position/left.rs +++ b/crates/hdx_ast/src/css/values/position/left.rs @@ -1 +1,36 @@ -pub type Left = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Left { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for Left {} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "-10px", "-10px"); + test_write::(&allocator, "auto", "auto"); + } +} diff --git a/crates/hdx_ast/src/css/values/position/right.rs b/crates/hdx_ast/src/css/values/position/right.rs index c583db70..6d622f4a 100644 --- a/crates/hdx_ast/src/css/values/position/right.rs +++ b/crates/hdx_ast/src/css/values/position/right.rs @@ -1 +1,36 @@ -pub type Right = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Right { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for Right {} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "-10px", "-10px"); + test_write::(&allocator, "auto", "auto"); + } +} diff --git a/crates/hdx_ast/src/css/values/position/top.rs b/crates/hdx_ast/src/css/values/position/top.rs index 7fb4ef72..1fbb6a33 100644 --- a/crates/hdx_ast/src/css/values/position/top.rs +++ b/crates/hdx_ast/src/css/values/position/top.rs @@ -1 +1,36 @@ -pub type Top = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{css::values::units::LengthPercentage, Parsable, Value, Writable}; + +#[derive(Parsable, Writable, Default, PartialEq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum Top { + #[default] + Auto, // atom!("auto"), + #[parsable(DimensionOrZero, FromToken)] + LengthPercentage(LengthPercentage), +} + +impl Value for Top {} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + + use super::*; + use crate::test_helpers::test_write; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 8); + } + + #[test] + fn test_writes() { + let allocator = Allocator::default(); + test_write::(&allocator, "-10px", "-10px"); + test_write::(&allocator, "auto", "auto"); + } +} diff --git a/crates/hdx_ast/src/css/values/sizing/max_width.rs b/crates/hdx_ast/src/css/values/sizing/max_width.rs index a3144721..d2aef9c5 100644 --- a/crates/hdx_ast/src/css/values/sizing/max_width.rs +++ b/crates/hdx_ast/src/css/values/sizing/max_width.rs @@ -2,7 +2,7 @@ use serde::Serialize; use super::super::units::LengthPercentage; -use crate::{Parsable, Writable}; +use crate::{Parsable, Writable, traits::Value}; // https://drafts.csswg.org/css-sizing-4/#sizing-values #[derive(Parsable, Writable, Default, Debug, PartialEq, Hash)] @@ -24,6 +24,12 @@ pub enum MaxWidth { FitContentFunction(LengthPercentage), } +impl Value for MaxWidth { + fn initial() -> MaxWidth { + MaxWidth::None + } +} + #[cfg(test)] mod tests { use oxc_allocator::Allocator; diff --git a/crates/hdx_ast/src/css/values/sizing/width.rs b/crates/hdx_ast/src/css/values/sizing/width.rs index de04a3d1..d7611c10 100644 --- a/crates/hdx_ast/src/css/values/sizing/width.rs +++ b/crates/hdx_ast/src/css/values/sizing/width.rs @@ -2,7 +2,7 @@ use serde::Serialize; use super::super::units::LengthPercentage; -use crate::{Parsable, Writable}; +use crate::{Parsable, Writable, Value}; // https://drafts.csswg.org/css-sizing-4/#sizing-values #[derive(Parsable, Writable, Default, Debug, PartialEq, Hash)] @@ -24,6 +24,8 @@ pub enum Width { FitContentFunction(LengthPercentage), } +impl Value for Width {} + #[cfg(test)] mod tests { use oxc_allocator::Allocator; diff --git a/crates/hdx_ast/src/css/values/text/text_align.rs b/crates/hdx_ast/src/css/values/text/text_align.rs index 37d8b065..fef3efd8 100644 --- a/crates/hdx_ast/src/css/values/text/text_align.rs +++ b/crates/hdx_ast/src/css/values/text/text_align.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Writable, Value}; // https://drafts.csswg.org/css-text-4/#propdef-text-align #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -19,6 +19,12 @@ pub enum TextAlign { * TODO: Custom? */ } +impl Value for TextAlign { + fn inherits() -> bool { + true + } +} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/text/text_align_last.rs b/crates/hdx_ast/src/css/values/text/text_align_last.rs index c42c7a7f..19b30efc 100644 --- a/crates/hdx_ast/src/css/values/text/text_align_last.rs +++ b/crates/hdx_ast/src/css/values/text/text_align_last.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Writable, Value}; // https://drafts.csswg.org/css-text-4/#propdef-text-align-last #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -18,6 +18,12 @@ pub enum TextAlignLast { MatchParent, // atom!("match-parent") } +impl Value for TextAlignLast { + fn inherits() -> bool { + true + } +} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/text/white_space_trim.rs b/crates/hdx_ast/src/css/values/text/white_space_trim.rs index 049f2802..8fd5b420 100644 --- a/crates/hdx_ast/src/css/values/text/white_space_trim.rs +++ b/crates/hdx_ast/src/css/values/text/white_space_trim.rs @@ -5,7 +5,7 @@ use hdx_writer::{CssWriter, Result as WriterResult, WriteCss}; #[cfg(feature = "serde")] use serde::Serialize; -use crate::{bitmask, Atomizable}; +use crate::{bitmask, Atomizable, Value}; // https://drafts.csswg.org/css-text-4/#propdef-white-space-trim #[derive(Default, Atomizable)] @@ -19,6 +19,8 @@ pub enum WhiteSpaceTrim { DiscardInner = 0b0100, // atom!("discard-inner") } +impl Value for WhiteSpaceTrim {} + impl<'a> Parse<'a> for WhiteSpaceTrim { fn parse(parser: &mut Parser<'a>) -> ParserResult> { let span = parser.span(); diff --git a/crates/hdx_ast/src/css/values/text_decor/text_decoration_line.rs b/crates/hdx_ast/src/css/values/text_decor/text_decoration_line.rs index f2f8129d..dfc4730c 100644 --- a/crates/hdx_ast/src/css/values/text_decor/text_decoration_line.rs +++ b/crates/hdx_ast/src/css/values/text_decor/text_decoration_line.rs @@ -5,7 +5,7 @@ use hdx_writer::{CssWriter, Result as WriterResult, WriteCss}; #[cfg(feature = "serde")] use serde::Serialize; -use crate::bitmask; +use crate::{bitmask, Value}; // https://drafts.csswg.org/css-text/#text-align-property #[derive(Default)] @@ -20,6 +20,8 @@ pub enum TextDecorationLine { Blink = 0b1000, } +impl Value for TextDecorationLine {} + impl<'a> Parse<'a> for TextDecorationLine { fn parse(parser: &mut Parser<'a>) -> ParserResult> { let span = parser.span(); diff --git a/crates/hdx_ast/src/css/values/ui/appearance.rs b/crates/hdx_ast/src/css/values/ui/appearance.rs index 05ce7049..241630ea 100644 --- a/crates/hdx_ast/src/css/values/ui/appearance.rs +++ b/crates/hdx_ast/src/css/values/ui/appearance.rs @@ -1 +1,28 @@ -pub type Appearance = super::super::Todo; +#[cfg(feature = "serde")] +use serde::Serialize; + +use crate::{Atomizable, Parsable, Writable, Value}; + +// https://drafts.csswg.org/css-ui/#appearance-switching +#[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "kebab-case"))] +pub enum Appearance { + None, // atom!("none") + #[default] + Auto, // atom!("auto") + // + Searchfield, // atom!("searchfield") + Textarea, // atom!("textarea") + Checkbox, // atom!("checkbox") + Radio, // atom!("radio") + Menulist, // atom!("menulist") + Listbox, // atom!("listbox") + Meter, // atom!("meter") + ProgressBar, // atom!("progress-bar") + Button, // atom!("button") + // + Textfield, // atom!("textfield") + MenulistButton, // atom!("menulist-button") +} + +impl Value for Appearance {} diff --git a/crates/hdx_ast/src/css/values/ui/cursor.rs b/crates/hdx_ast/src/css/values/ui/cursor.rs index 676617fe..ee3ee3d7 100644 --- a/crates/hdx_ast/src/css/values/ui/cursor.rs +++ b/crates/hdx_ast/src/css/values/ui/cursor.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] use serde::Serialize; -use crate::{Atomizable, Parsable, Writable}; +use crate::{Atomizable, Parsable, Value, Writable}; // https://drafts.csswg.org/css-ui-4/#propdef-cursor #[derive(Parsable, Writable, Atomizable, Default, Debug, PartialEq, Hash)] @@ -47,6 +47,12 @@ pub enum Cursor { * TODO: Custom? */ } +impl Value for Cursor { + fn inherits() -> bool { + true + } +} + #[cfg(test)] mod tests { diff --git a/crates/hdx_ast/src/css/values/units/float.rs b/crates/hdx_ast/src/css/values/units/float.rs index fd7e2578..2f125d18 100644 --- a/crates/hdx_ast/src/css/values/units/float.rs +++ b/crates/hdx_ast/src/css/values/units/float.rs @@ -5,6 +5,7 @@ use std::{ }; use hdx_derive::Writable; +use hdx_parser::FromToken; #[cfg(feature = "serde")] use serde::Serialize; diff --git a/crates/hdx_ast/src/css/values/units/length.rs b/crates/hdx_ast/src/css/values/units/length.rs index f47a2470..3f1d4182 100644 --- a/crates/hdx_ast/src/css/values/units/length.rs +++ b/crates/hdx_ast/src/css/values/units/length.rs @@ -19,10 +19,11 @@ macro_rules! length { $name: ident: $atom: tt, )+ ) => { - #[derive(Writable, Debug, Clone, Copy, PartialEq, Hash)] + #[derive(Writable, Default, Debug, Clone, Copy, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum Length { #[writable(rename = "0")] + #[default] Zero, $( #[writable(suffix = $atom)] @@ -58,9 +59,10 @@ macro_rules! length { } } - #[derive(Writable, Debug, Clone, Copy, PartialEq, Hash)] + #[derive(Writable, Default, Debug, Clone, Copy, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum LengthPercentage { + #[default] #[writable(rename = "0")] Zero, $( @@ -75,6 +77,7 @@ macro_rules! length { pub fn new(val: CSSFloat, atom: Atom) -> Option { match atom { $(atom!($atom) => Some(LengthPercentage::$name(val)),)+ + atom!("%") => Some(LengthPercentage::Percent(val)), _ => None } } @@ -182,5 +185,7 @@ mod tests { test_write::(&allocator, "1.2345678901234px", "1.2345679px"); // Removes redundant dp test_write::(&allocator, "-1.0px", "-1px"); + // Percent + test_write::(&allocator, "1%", "1%"); } } diff --git a/crates/hdx_ast/src/lib.rs b/crates/hdx_ast/src/lib.rs index 89ebc647..3a175a70 100644 --- a/crates/hdx_ast/src/lib.rs +++ b/crates/hdx_ast/src/lib.rs @@ -13,7 +13,7 @@ pub(crate) use hdx_atom::{atom, Atom, Atomizable}; pub(crate) use hdx_derive::{Atomizable, Parsable, Writable}; pub(crate) use hdx_parser::{Box, Spanned, Vec}; pub(crate) use macros::*; -pub use traits::Unit; +pub use traits::Value; pub trait ToSpecificity: Sized { fn specificity(&self) -> Specificity; diff --git a/crates/hdx_ast/src/macros.rs b/crates/hdx_ast/src/macros.rs index e3c442a5..e69de29b 100644 --- a/crates/hdx_ast/src/macros.rs +++ b/crates/hdx_ast/src/macros.rs @@ -1,69 +0,0 @@ -macro_rules! length_percentage_struct { - ($name: ident) => { - #[derive(Debug, Hash)] - #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())] - pub struct $name { - length: $crate::css::values::units::LengthPercentage, - } - - impl<'a> hdx_writer::WriteCss<'a> for $name { - fn write_css(&self, sink: &mut W) -> hdx_writer::Result { - self.length.write_css(sink) - } - } - } -} -pub(crate) use length_percentage_struct; - -macro_rules! positive_length_percentage_property { - ($name: ident) => { - $crate::length_percentage_struct!($name); - impl<'a> hdx_parser::Parse<'a> for $name { - fn parse(parser: &mut hdx_parser::Parser<'a>) -> hdx_parser::Result> { - use hdx_lexer::Token; - use $crate::css::values::units::LengthPercentage; - let span = parser.span(); - match parser.cur() { - Token::Number(n, _) if n == 0.0 => Ok(Self { length: LengthPercentage::Zero }.spanned(span)), - Token::Dimension(n, unit, _) => { - if n < 0.0 { - Err(hdx_parser::diagnostics::NumberNotNegative(n, span))? - } - if let Some(length) = LengthPercentage::new(n.into(), unit.clone()) { - Ok(Self { length }.spanned(span)) - } else { - Err(hdx_parser::diagnostics::UnexpectedDimension(unit, span))? - } - } - token => hdx_parser::unexpected!(parser, token), - } - } - } - }; -} -pub(crate) use positive_length_percentage_property; - -macro_rules! length_percentage_property { - ($name: ident) => { - $crate::length_percentage_struct!($name); - impl<'a> hdx_parser::Parse<'a> for $name { - fn parse(parser: &mut hdx_parser::Parser<'a>) -> hdx_parser::Result> { - use hdx_lexer::Token; - use $crate::css::values::units::LengthPercentage; - let span = parser.span(); - match parser.cur() { - Token::Number(n, _) if n == 0.0 => Ok(Self { length: LengthPercentage::Zero }.spanned(span)), - Token::Dimension(n, unit, _) => { - if let Some(length) = LengthPercentage::new(n.into(), unit.clone()) { - Ok(Self { length }.spanned(span)) - } else { - Err(hdx_parser::diagnostics::UnexpectedDimension(unit, span))? - } - } - token => hdx_parser::unexpected!(parser, token), - } - } - } - }; -} -pub(crate) use length_percentage_property; diff --git a/crates/hdx_ast/src/syntax.rs b/crates/hdx_ast/src/syntax.rs index 9530a199..bb8a1734 100644 --- a/crates/hdx_ast/src/syntax.rs +++ b/crates/hdx_ast/src/syntax.rs @@ -1,7 +1,7 @@ use hdx_atom::{atom, Atom}; use hdx_lexer::{PairWise, Token}; use hdx_parser::{ - discard, expect, unexpected, AtRule as AtRuleTrait, Block as BlockTrait, Box, Parse, Parser, + expect, unexpected, AtRule as AtRuleTrait, Block as BlockTrait, Box, Parse, Parser, QualifiedRule as QualifiedRuleTrait, Result as ParserResult, Span, Spanned, State, Vec, }; use hdx_writer::{CssWriter, Result as WriterResult, WriteCss}; @@ -194,7 +194,8 @@ impl<'a> Parse<'a> for Rule<'a> { Ok(match parser.cur() { Token::AtKeyword(_) => Rule::AtRule(AtRule::parse(parser)?), _ => Rule::QualifiedRule(QualifiedRule::parse(parser)?), - }.spanned(span.end(parser.pos()))) + } + .spanned(span.end(parser.pos()))) } } diff --git a/crates/hdx_ast/src/traits.rs b/crates/hdx_ast/src/traits.rs index 587666d3..d2e76650 100644 --- a/crates/hdx_ast/src/traits.rs +++ b/crates/hdx_ast/src/traits.rs @@ -1,6 +1,9 @@ -use crate::Atomizable; -pub trait Unit: Sized { - type Unit: Atomizable + Default; +pub trait Value: PartialEq + Default + Sized { + fn initial() -> Self { + Self::default() + } - fn new(value: f32, unit: Self::Unit) -> Self; + fn inherits() -> bool { + false + } } diff --git a/crates/hdx_derive/src/parsable.rs b/crates/hdx_derive/src/parsable.rs index 631a4b28..2cd19645 100644 --- a/crates/hdx_derive/src/parsable.rs +++ b/crates/hdx_derive/src/parsable.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::Parse, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Fields, - FieldsUnnamed, Ident, LitStr, Meta, Token, + FieldsUnnamed, Ident, LitStr, Meta, Token, DataStruct, }; use crate::{err, kebab}; @@ -100,7 +100,43 @@ impl ParsableArgs { pub fn derive(input: DeriveInput) -> TokenStream { let ident = input.ident; match input.data { - Data::Struct(_) => err(ident.span(), "Cannot derive Parsable on a struct with named or no fields"), + Data::Struct(DataStruct { fields: Fields::Unnamed(fields), .. }) => { + if fields.unnamed.len() != 1 { + err(ident.span(), "Cannot derive Parsable on a struct with multiple unnamed fields") + } else { + let field = fields.unnamed.first().unwrap(); + let field_ty = &field.ty; + let args = ParsableArgs::parse(&field.attrs); + let value = if args.parse_inner { + quote! { + Self(#field_ty::parse(parser)?).spanned(span.end(parser.pos())) + } + } else if args.from_token { + quote! { + if let Some(value) = #field_ty::from_token(parser.cur()) { + parser.advance(); + Self(value).spanned(span.end(parser.pos())) + } else { + hdx_parser::unexpected!(parser) + } + } + } else { + return err(ident.span(), "Cannot derive Parsable on a struct without marking ParseInner or FromToken") + }; + quote! { + #[automatically_derived] + impl<'a> hdx_parser::Parse<'a> for #ident { + fn parse(parser: &mut hdx_parser::Parser<'a>) -> hdx_parser::Result> { + use hdx_parser::{Parse, FromToken}; + let span = parser.span(); + Ok(#value) + } + } + } + } + } + + Data::Struct(_) => err(ident.span(), "Cannot derive Parsable on a struct with named fields"), Data::Union(_) => err(ident.span(), "Cannot derive Parsable on a Union"), diff --git a/crates/hdx_wasm/Cargo.toml b/crates/hdx_wasm/Cargo.toml index 71253403..dc8cd677 100644 --- a/crates/hdx_wasm/Cargo.toml +++ b/crates/hdx_wasm/Cargo.toml @@ -56,4 +56,5 @@ debug = true panic = "abort" [package.metadata.wasm-pack.profile.release] -wasm-opt = ['-O4'] +wasm-opt = false +# wasm-opt = ['-O4'] diff --git a/website/package.json b/website/package.json index e74e76b6..684997cc 100644 --- a/website/package.json +++ b/website/package.json @@ -6,7 +6,7 @@ "postinstall": "[ \"$NODE_ENV\" = \"production\" ] && npm run build || npm run build:dev", "clean-old": "ls -t playground/ | grep hdx | tail -n +2 | xargs rm -f", "postbuild": "npm run clean-old", - "prestart": "[ \"$NODE_ENV\" = \"production\" ] && npm run wasm-build || npm run build:dev", + "prestart": "npm run postinstall", "postbuild:dev": "npm run clean-old", "prebuild": "npm run wasm-build", "prebuild:dev": "npm run wasm-build:dev",