diff --git a/rsass/src/css/atrule.rs b/rsass/src/css/atrule.rs index 6f28dd23e..8b0122056 100644 --- a/rsass/src/css/atrule.rs +++ b/rsass/src/css/atrule.rs @@ -1,98 +1,10 @@ use super::{ - BodyItem, Comment, CustomProperty, Import, Property, Rule, Value, + BodyItem, Comment, CustomProperty, Import, MediaRule, Property, Rule, + Value, }; use crate::output::CssBuf; use std::io::{self, Write}; -/// An `@media rule in css. -#[derive(Clone, Debug)] -pub struct MediaRule { - args: MediaArgs, - body: Vec, -} - -impl MediaRule { - pub(crate) fn new(args: MediaArgs, body: Vec) -> Self { - MediaRule { args, body } - } - pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> { - buf.do_indent_no_nl(); - buf.add_str("@media "); - self.args.write(buf)?; - //if !self.args.is_null() { - //write!(buf, " {}", self.args.format(buf.format()))?; - //} - buf.start_block(); - for item in &self.body { - item.write(buf)?; - } - buf.end_block(); - Ok(()) - } -} - -/// The media selection argument of an `@media` rule. -#[derive(Clone, Debug)] -pub enum MediaArgs { - /// `all` media. - Name(String), - /// `(cond: valud)` media. - Condition(String, Value), - /// unary logic - Only(Box), - /// Any media subquery in parenthesis. - Paren(Box), - /// unary logic - Not(Box), - /// a and b and c media. - And(Vec), - /// a or b or c media. - Or(Vec), -} - -impl MediaArgs { - pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> { - match self { - MediaArgs::Name(name) => write!(buf, "{name}")?, - MediaArgs::Only(a) => { - buf.add_str("only "); - a.write(buf)?; - } - MediaArgs::Not(a) => { - buf.add_str("not "); - a.write(buf)?; - } - MediaArgs::Condition(c, v) => { - write!(buf, "({c}: {})", v.format(buf.format()))? - } - MediaArgs::And(args) => { - if let Some((first, rest)) = args.split_first() { - first.write(buf)?; - for arg in rest { - buf.add_str(" and "); - arg.write(buf)?; - } - } - } - MediaArgs::Or(args) => { - if let Some((first, rest)) = args.split_first() { - first.write(buf)?; - for arg in rest { - buf.add_str(" or "); - arg.write(buf)?; - } - } - } - MediaArgs::Paren(a) => { - buf.add_str("("); - a.write(buf)?; - buf.add_str(")"); - } - } - Ok(()) - } -} - /// An `@something` rule in css. /// /// Note that some well-known at rules (`@media`, `@keyframes`, ...) @@ -148,6 +60,8 @@ pub enum AtRuleBodyItem { Property(Property), /// A custom property declaration with a name and a value. CustomProperty(CustomProperty), + /// An `@media` rule. + MediaRule(MediaRule), /// An `@` rule. AtRule(AtRule), } @@ -160,6 +74,7 @@ impl AtRuleBodyItem { AtRuleBodyItem::Rule(rule) => rule.write(buf)?, AtRuleBodyItem::Property(property) => property.write(buf), AtRuleBodyItem::CustomProperty(cp) => cp.write(buf), + AtRuleBodyItem::MediaRule(rule) => rule.write(buf)?, AtRuleBodyItem::AtRule(rule) => rule.write(buf)?, } Ok(()) @@ -191,8 +106,8 @@ impl From for AtRuleBodyItem { } } impl From for AtRuleBodyItem { - fn from(_: MediaRule) -> Self { - todo!("This should not be needed") + fn from(rule: MediaRule) -> Self { + AtRuleBodyItem::MediaRule(rule) } } impl From for AtRuleBodyItem { diff --git a/rsass/src/css/binop.rs b/rsass/src/css/binop.rs index a45c23538..22dbb6d76 100644 --- a/rsass/src/css/binop.rs +++ b/rsass/src/css/binop.rs @@ -144,7 +144,9 @@ impl Display for Formatted<'_, BinOp> { } } (op, Value::BinOp(op2)) - if (op2.op < op) || (op == Minus && op2.op == Minus) => + if ((op2.op < op) + || (op == Minus && op2.op == Minus)) + && !(op.is_cmp() && op2.op.is_cmp()) => { (op, Value::Paren(Box::new(self.value.b.clone()))) } diff --git a/rsass/src/css/mediarule.rs b/rsass/src/css/mediarule.rs new file mode 100644 index 000000000..3a65123b1 --- /dev/null +++ b/rsass/src/css/mediarule.rs @@ -0,0 +1,139 @@ +use super::atrule::AtRuleBodyItem; +use super::Value; +use crate::output::{CssBuf, Format}; +use crate::parser::{css::media, input_span}; +use crate::value::Operator; +use crate::ParseError; +use std::io::{self, Write}; + +/// An `@media` rule in css. +#[derive(Clone, Debug)] +pub struct MediaRule { + args: MediaArgs, + body: Vec, +} + +impl MediaRule { + pub(crate) fn new(args: MediaArgs, body: Vec) -> Self { + MediaRule { args, body } + } + pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> { + if !self.body.is_empty() { + buf.do_indent_no_nl(); + buf.add_str("@media "); + self.args.write(buf)?; + buf.start_block(); + for item in &self.body { + item.write(buf)?; + } + buf.end_block(); + } + Ok(()) + } +} + +/// The media selection argument of an `@media` rule. +#[derive(Clone, Debug)] +pub enum MediaArgs { + /// A named media, such as `all` or `screen`. + Name(String), + /// `(cond: value)` media. + Condition(String, Value), + /// A condition / range, such as `(witdh < 14em)` or + /// `(14em <= width < 80em)`. + Range(Vec<(Operator, Value)>), + /// Any media subquery in parenthesis. + Paren(Box), + /// Any media subquery in square brackets. + Bracket(Box), + /// unary operation. + /// The operator (`not`, `only`) is a string to preserve case. + UnaryOp(String, Box), + /// a and b and c media. + Comma(Vec), + /// a and b and c media. + And(Vec), + /// a or b or c media. + Or(Vec), +} + +impl MediaArgs { + pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> { + match self { + MediaArgs::Name(name) => write!(buf, "{name}")?, + MediaArgs::UnaryOp(op, a) => { + buf.add_str(op); + buf.add_str(" "); + a.write(buf)?; + } + MediaArgs::Condition(c, v) => { + write!(buf, "({c}: {})", v.format(buf.format()))? + } + MediaArgs::Comma(args) => { + if let Some((first, rest)) = args.split_first() { + first.write(buf)?; + for arg in rest { + buf.add_one(", ", ","); + arg.write(buf)?; + } + } + } + MediaArgs::And(args) => { + if let Some((first, rest)) = args.split_first() { + first.write(buf)?; + for arg in rest { + buf.add_str(" and "); + arg.write(buf)?; + } + } + } + MediaArgs::Or(args) => { + if let Some((first, rest)) = args.split_first() { + first.write(buf)?; + for arg in rest { + buf.add_str(" or "); + arg.write(buf)?; + } + } + } + MediaArgs::Paren(a) => { + buf.add_str("("); + a.write(buf)?; + buf.add_str(")"); + } + MediaArgs::Bracket(a) => { + buf.add_str("["); + a.write(buf)?; + buf.add_str("]"); + } + MediaArgs::Range(v) => { + buf.add_str("("); + if let Some(((_op, first), rest)) = v.split_first() { + buf.add_str( + &first.to_string(buf.format()).replace('\n', " "), + ); + for (op, val) in rest { + write!(buf, " {} ", op)?; + buf.add_str( + &val.to_string(buf.format()).replace('\n', " "), + ); + } + } + buf.add_str(")"); + } + } + Ok(()) + } +} + +// Note: I'm not sure printing and parsing is the best way to do this, +// but then, I'm not sure media arguments in scss are best represented +// as a Value either. +impl TryFrom for MediaArgs { + type Error = crate::Error; + + fn try_from(value: Value) -> Result { + let value = value.format(Format::default()).to_string(); + Ok(ParseError::check(media::args(input_span(value).borrow()))?) + } +} diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 9085c8588..8976811a2 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -4,6 +4,7 @@ mod binop; mod call_args; mod comment; mod item; +mod mediarule; mod rule; mod selectors; mod string; @@ -11,11 +12,12 @@ mod util; mod value; mod valueformat; -pub use self::atrule::{AtRule, AtRuleBodyItem, MediaArgs, MediaRule}; +pub use self::atrule::{AtRule, AtRuleBodyItem}; pub use self::binop::BinOp; pub use self::call_args::CallArgs; pub use self::comment::Comment; pub use self::item::{Import, Item}; +pub use self::mediarule::{MediaArgs, MediaRule}; pub use self::rule::{BodyItem, CustomProperty, Property, Rule}; pub use self::selectors::{BadSelector, Selector, SelectorPart, Selectors}; pub use self::string::CssString; diff --git a/rsass/src/output/cssdata.rs b/rsass/src/output/cssdata.rs index 944395804..223492df4 100644 --- a/rsass/src/output/cssdata.rs +++ b/rsass/src/output/cssdata.rs @@ -1,6 +1,10 @@ -use super::cssdest::{AtRuleDest, CssDestination, NsRuleDest, RuleDest}; +use super::cssdest::{ + AtMediaDest, AtRuleDest, CssDestination, NsRuleDest, RuleDest, +}; use super::{CssBuf, Format}; -use crate::css::{Comment, CssString, Import, Item, Selectors, Value}; +use crate::css::{ + Comment, CssString, Import, Item, MediaArgs, Selectors, Value, +}; use crate::{Error, Invalid, ScopeRef}; use std::collections::BTreeMap; @@ -85,6 +89,9 @@ impl CssDestination for CssData { fn start_rule(&mut self, selectors: Selectors) -> Result { Ok(RuleDest::new(self, selectors)) } + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { + AtMediaDest::new(self, args) + } fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest { AtRuleDest::new(self, name, args) } diff --git a/rsass/src/output/cssdest.rs b/rsass/src/output/cssdest.rs index 128f43925..7e9f12ee2 100644 --- a/rsass/src/output/cssdest.rs +++ b/rsass/src/output/cssdest.rs @@ -1,7 +1,7 @@ use super::CssData; use crate::css::{ AtRule, AtRuleBodyItem, Comment, CssString, CustomProperty, Import, Item, - Property, Rule, Selectors, Value, + MediaArgs, MediaRule, Property, Rule, Selectors, Value, }; use crate::Invalid; @@ -11,6 +11,7 @@ pub trait CssDestination { fn head(&mut self) -> &mut CssData; fn start_rule(&mut self, selectors: Selectors) -> Result; + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest; fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest; fn start_nsrule(&mut self, name: String) -> Result; @@ -71,6 +72,15 @@ impl<'a> CssDestination for RuleDest<'a> { fn start_rule(&mut self, selectors: Selectors) -> Result { Ok(RuleDest::new(self, selectors)) } + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { + let selectors = self.rule.selectors.clone(); + AtMediaDest { + parent: self, + args, + rule: Some(Rule::new(selectors)), + body: Vec::new(), + } + } fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest { let selectors = self.rule.selectors.clone(); AtRuleDest { @@ -131,6 +141,9 @@ impl<'a> CssDestination for NsRuleDest<'a> { fn start_rule(&mut self, _selectors: Selectors) -> Result { Err(Invalid::InNsRule) } + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { + AtMediaDest::new(self, args) + } fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest { AtRuleDest { parent: self, @@ -210,6 +223,131 @@ impl<'a> CssDestination for AtRuleDest<'a> { fn start_rule(&mut self, selectors: Selectors) -> Result { Ok(RuleDest::new(self, selectors)) } + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { + let rule = self.rule.as_ref().map(|r| Rule::new(r.selectors.clone())); + AtMediaDest { + parent: self, + args, + rule, + body: Vec::new(), + } + } + fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest { + let rule = self.rule.as_ref().map(|r| Rule::new(r.selectors.clone())); + AtRuleDest { + parent: self, + name, + args, + rule, + body: Vec::new(), + } + } + fn start_nsrule(&mut self, name: String) -> Result { + Ok(NsRuleDest { parent: self, name }) + } + + fn push_import(&mut self, import: Import) { + self.body.push(import.into()); + } + + fn push_comment(&mut self, c: Comment) { + if let Some(rule) = &mut self.rule { + rule.push(c.into()); + } else { + self.body.push(c.into()); + } + } + + fn push_item(&mut self, item: Item) -> Result { + self.body.push(match item { + Item::Comment(c) => c.into(), + Item::Import(i) => i.into(), + Item::Rule(r) => r.into(), + // FIXME: This should bubble or something? + Item::MediaRule(r) => r.into(), + Item::AtRule(r) => r.into(), + Item::Separator => return Ok(()), // Not pushed? + }); + Ok(()) + } + + fn push_property(&mut self, name: String, value: Value) -> Result { + let prop = Property::new(name, value); + if let Some(rule) = &mut self.rule { + rule.push(prop.into()); + } else { + self.body.push(prop.into()); + } + Ok(()) + } + + fn push_custom_property( + &mut self, + name: String, + value: CssString, + ) -> Result { + if let Some(rule) = &mut self.rule { + rule.push(CustomProperty::new(name, value).into()); + Ok(()) + } else { + Err(Invalid::GlobalCustomProperty) + } + } +} + +pub struct AtMediaDest<'a> { + parent: &'a mut dyn CssDestination, + args: MediaArgs, + rule: Option, + body: Vec, +} +impl<'a> AtMediaDest<'a> { + pub fn new(parent: &'a mut dyn CssDestination, args: MediaArgs) -> Self { + AtMediaDest { + parent, + args, + rule: None, + body: Vec::new(), + } + } +} + +impl<'a> Drop for AtMediaDest<'a> { + fn drop(&mut self) { + let mut body = Vec::new(); + std::mem::swap(&mut self.body, &mut body); + let mut args = MediaArgs::Name(String::new()); + std::mem::swap(&mut self.args, &mut args); + if let Some(rule) = &self.rule { + if !rule.body.is_empty() { + body.insert(0, rule.clone().into()); + } + } + let result = MediaRule::new(args, body); + if let Err(err) = self.parent.push_item(result.into()) { + eprintln!("Error ending AtRuleDest: {}", err); + } + self.parent.separate(); + } +} + +impl<'a> CssDestination for AtMediaDest<'a> { + fn head(&mut self) -> &mut CssData { + self.parent.head() + } + + fn start_rule(&mut self, selectors: Selectors) -> Result { + Ok(RuleDest::new(self, selectors)) + } + fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { + let rule = self.rule.as_ref().map(|r| Rule::new(r.selectors.clone())); + AtMediaDest { + parent: self, + args, + rule, + body: Vec::new(), + } + } fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest { let rule = self.rule.as_ref().map(|r| Rule::new(r.selectors.clone())); AtRuleDest { @@ -241,6 +379,8 @@ impl<'a> CssDestination for AtRuleDest<'a> { Item::Comment(c) => c.into(), Item::Import(i) => i.into(), Item::Rule(r) => r.into(), + // FIXME: Check if the args can be merged! + // Or is that a separate pass after building a first css tree? Item::MediaRule(r) => r.into(), Item::AtRule(r) => r.into(), Item::Separator => return Ok(()), // Not pushed? diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index 7a444cba8..69348e669 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -213,17 +213,12 @@ fn handle_item( handle_body(body, dest, subscope, file_context)?; } } - Item::AtMedia { args, body, pos } => { + Item::AtMedia { args, body, pos: _ } => { let args = args.evaluate(scope.clone())?; - let name = "media".into(); - // TODO: Make specific AtRule item for css as well! + let mut atmedia = dest.start_atmedia(args.try_into()?); if let Some(ref body) = *body { - let mut atrule = dest.start_atrule(name, args); let local = ScopeRef::sub(scope); - handle_body(body, &mut atrule, local, file_context)?; - } else { - dest.push_item(AtRule::new(name, args, None).into()) - .at(pos)?; + handle_body(body, &mut atmedia, local, file_context)?; } } Item::AtRule { diff --git a/rsass/src/parser/css/media.rs b/rsass/src/parser/css/media.rs new file mode 100644 index 000000000..4c567cc79 --- /dev/null +++ b/rsass/src/parser/css/media.rs @@ -0,0 +1,144 @@ +use super::super::util::{ignore_comments, opt_spacelike, spacelike}; +use super::{relational_operator, strings, values, PResult, Span}; +use crate::css::{MediaArgs, Value}; +use crate::value::{ListSeparator, Operator}; +use nom::branch::alt; +use nom::bytes::complete::{tag, tag_no_case}; +use nom::combinator::map; +use nom::multi::{fold_many0, fold_many1, separated_list1}; +use nom::sequence::{delimited, pair, preceded, terminated}; +use std::str::from_utf8; + +pub fn args(input: Span) -> PResult { + map( + separated_list1( + delimited(opt_spacelike, tag(","), spacelike), + media_args_and, + ), + |v| { + if v.len() == 1 { + v.into_iter().next().unwrap() + } else { + MediaArgs::Comma(v) + } + }, + )(input) +} +fn media_args_and(input: Span) -> PResult { + map( + separated_list1( + delimited(opt_spacelike, tag_no_case("and"), ignore_comments), + media_args_or, + ), + |v| { + if v.len() == 1 { + v.into_iter().next().unwrap() + } else { + MediaArgs::And(v) + } + }, + )(input) +} + +fn media_args_or(input: Span) -> PResult { + map( + separated_list1( + delimited(opt_spacelike, tag_no_case("or"), ignore_comments), + media_args_one, + ), + |v| { + if v.len() == 1 { + v.into_iter().next().unwrap() + } else { + MediaArgs::Or(v) + } + }, + )(input) +} + +fn media_args_one(input: Span) -> PResult { + alt(( + map( + pair( + terminated( + alt((tag_no_case("not"), tag_no_case("only"))), + ignore_comments, + ), + media_args_one, + ), + |(op, a)| { + MediaArgs::UnaryOp( + from_utf8(&op).unwrap().into(), + Box::new(a), + ) + }, + ), + map(strings::css_string, MediaArgs::Name), + delimited( + tag("("), + alt(( + map( + pair( + terminated(strings::css_string, tag(": ")), + values::any, + ), + |(k, v)| MediaArgs::Condition(k, v), + ), + map(media_relation, MediaArgs::Range), + map(args, |v| MediaArgs::Paren(Box::new(v))), + )), + preceded(opt_spacelike, tag(")")), + ), + delimited( + tag("["), + map(media_args_one, |v| MediaArgs::Bracket(Box::new(v))), + tag("]"), + ), + ))(input) +} + +fn media_relation(input: Span) -> PResult> { + let (rest, first) = media_value(input)?; + fold_many1( + pair( + delimited(opt_spacelike, relational_operator, opt_spacelike), + media_value, + ), + move || vec![(Operator::Equal, first.clone())], + |mut acc, item| { + acc.push(item); + acc + }, + )(rest) +} + +/// Any css value that is allowd in a media relation / range. +fn media_value(input: Span) -> PResult { + alt(( + media_slash_list_no_space, + delimited( + terminated(tag("("), opt_spacelike), + media_value, + preceded(opt_spacelike, tag(")")), + ), + ))(input) +} +pub fn media_slash_list_no_space(input: Span) -> PResult { + let (input, first) = values::single(input)?; + let (input, list) = fold_many0( + preceded(tag("/"), values::single), + move || vec![first.clone()], + |mut list: Vec, item| { + list.push(item); + list + }, + )(input)?; + Ok(( + input, + if list.len() == 1 { + list.into_iter().next().unwrap() + } else { + Value::List(list, Some(ListSeparator::SlashNoSpace), false) + }, + )) +} diff --git a/rsass/src/parser/css/mod.rs b/rsass/src/parser/css/mod.rs index 7691e4170..03bd81761 100644 --- a/rsass/src/parser/css/mod.rs +++ b/rsass/src/parser/css/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod media; mod rule; mod selectors; mod strings; @@ -5,18 +6,17 @@ mod values; pub(crate) use self::selectors::{selector, selector_part, selectors}; -use super::util::spacelike; -use super::{util::opt_spacelike, PResult, Span}; -use crate::css::{ - AtRule, Comment, Import, Item, MediaArgs, MediaRule, Value, -}; +use super::util::{opt_spacelike, spacelike}; +use super::{PResult, Span}; +use crate::css::{AtRule, Comment, Import, Item, MediaRule, Value}; +use crate::value::Operator; use nom::branch::alt; use nom::bytes::complete::{is_not, tag, tag_no_case}; use nom::combinator::{ - all_consuming, into, map, map_res, not, opt, peek, recognize, + all_consuming, into, map, map_res, not, opt, peek, recognize, value, }; -use nom::multi::{fold_many0, many0, many_till, separated_list1}; -use nom::sequence::{delimited, pair, preceded, terminated}; +use nom::multi::{fold_many0, many0, many_till}; +use nom::sequence::{delimited, preceded, terminated}; use std::str::from_utf8; pub fn file(input: Span) -> PResult> { @@ -47,7 +47,7 @@ fn top_level_item(input: Span) -> PResult { "import" => into(import2)(input), "media" => { let (input, args) = - preceded(spacelike, media_args)(input)?; + preceded(spacelike, media::args)(input)?; let (input, body) = preceded( opt_spacelike, delimited( @@ -113,66 +113,15 @@ fn import2(input: Span) -> PResult { )(input) } -fn media_args(input: Span) -> PResult { - map( - separated_list1( - delimited(opt_spacelike, tag_no_case("and"), spacelike), - media_args_or, - ), - |v| { - if v.len() == 1 { - v.into_iter().next().unwrap() - } else { - MediaArgs::And(v) - } - }, - )(input) -} - -fn media_args_or(input: Span) -> PResult { - map( - separated_list1( - delimited(opt_spacelike, tag_no_case("or"), spacelike), - media_args_one, - ), - |v| { - if v.len() == 1 { - v.into_iter().next().unwrap() - } else { - MediaArgs::Or(v) - } - }, - )(input) -} - -fn media_args_one(input: Span) -> PResult { +pub fn relational_operator(input: Span) -> PResult { alt(( - map( - preceded( - terminated(tag_no_case("not"), spacelike), - media_args_one, - ), - |a| MediaArgs::Not(Box::new(a)), - ), - map( - preceded( - terminated(tag_no_case("only"), spacelike), - media_args_one, - ), - |a| MediaArgs::Only(Box::new(a)), - ), - map(strings::css_string, MediaArgs::Name), - map( - delimited( - tag("("), - pair(terminated(strings::css_string, tag(": ")), values::any), - tag(")"), - ), - |(k, v)| MediaArgs::Condition(k, v), - ), - map(delimited(tag("("), media_args, tag(")")), |v| { - MediaArgs::Paren(Box::new(v)) - }), + value(Operator::Equal, tag("==")), + value(Operator::EqualSingle, tag("=")), + value(Operator::NotEqual, tag("!=")), + value(Operator::GreaterE, tag(">=")), + value(Operator::Greater, tag(">")), + value(Operator::LesserE, tag("<=")), + value(Operator::Lesser, tag("<")), ))(input) } diff --git a/rsass/src/parser/css/values.rs b/rsass/src/parser/css/values.rs index 6baec0e64..9dd304942 100644 --- a/rsass/src/parser/css/values.rs +++ b/rsass/src/parser/css/values.rs @@ -5,7 +5,7 @@ use crate::parser::value::numeric; use crate::value::ListSeparator; use nom::branch::alt; use nom::bytes::complete::tag; -use nom::combinator::{into, peek}; +use nom::combinator::{into, map, peek}; use nom::multi::{fold_many0, many0}; use nom::sequence::{delimited, pair, preceded, terminated}; @@ -32,11 +32,11 @@ pub fn any(input: Span) -> PResult { )) } pub fn slash_list(input: Span) -> PResult { - let (input, first) = space_list(input)?; + let (input, first) = slash_list_no_space(input)?; let (input, list) = fold_many0( preceded( delimited(opt_spacelike, tag("/"), opt_spacelike), - space_list, + slash_list_no_space, ), move || vec![first.clone()], |mut list: Vec, item| { @@ -53,6 +53,25 @@ pub fn slash_list(input: Span) -> PResult { }, )) } +pub fn slash_list_no_space(input: Span) -> PResult { + let (input, first) = space_list(input)?; + let (input, list) = fold_many0( + preceded(tag("/"), space_list), + move || vec![first.clone()], + |mut list: Vec, item| { + list.push(item); + list + }, + )(input)?; + Ok(( + input, + if list.len() == 1 { + list.into_iter().next().unwrap() + } else { + Value::List(list, Some(ListSeparator::SlashNoSpace), false) + }, + )) +} pub fn space_list(input: Span) -> PResult { let (input, first) = single(input)?; let (input, list) = fold_many0( @@ -73,8 +92,22 @@ pub fn space_list(input: Span) -> PResult { )) } -fn single(input: Span) -> PResult { - alt((into(numeric), string_or_call))(input) +pub fn single(input: Span) -> PResult { + match input.first() { + Some(b'[') => map( + delimited( + terminated(tag("["), opt_spacelike), + any, + preceded(opt_spacelike, tag("]")), + ), + |v| match v { + Value::List(v, sep, false) => Value::List(v, sep, true), + v => Value::List(vec![v], Default::default(), true), + }, + )(input), + Some(c) if b'0' <= *c && *c <= b'9' => into(numeric)(input), + _ => string_or_call(input), + } } fn string_or_call(input: Span) -> PResult { diff --git a/rsass/src/parser/mod.rs b/rsass/src/parser/mod.rs index 3a638a0f3..52b56f74c 100644 --- a/rsass/src/parser/mod.rs +++ b/rsass/src/parser/mod.rs @@ -10,6 +10,7 @@ mod unit; pub(crate) mod util; pub mod value; +use self::css::relational_operator; pub(crate) use self::strings::name; pub use error::ParseError; pub(crate) use span::DebugBytes; @@ -25,11 +26,14 @@ use self::util::{ spacelike, }; use self::value::{ - dictionary, function_call, single_value, value_expression, + any_additive_expr, any_product, bracket_list, dictionary, function_call, + single_value, value_expression, variable, }; use crate::input::{SourceFile, SourceName, SourcePos}; use crate::sass::parser::{variable_declaration2, variable_declaration_mod}; -use crate::sass::{Callable, FormalArgs, Item, Name, Selectors, Value}; +use crate::sass::{ + BinOp, Callable, FormalArgs, Item, Name, Selectors, Value, +}; use crate::value::ListSeparator; #[cfg(test)] use crate::value::{Numeric, Unit}; @@ -42,7 +46,7 @@ use nom::combinator::{ all_consuming, into, map, map_res, opt, peek, value, verify, }; use nom::multi::{many0, many_till, separated_list0, separated_list1}; -use nom::sequence::{delimited, pair, preceded, terminated}; +use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::IResult; use std::str::{from_utf8, Utf8Error}; @@ -347,28 +351,35 @@ fn media_args(input: Span) -> PResult { preceded(tag(","), opt_spacelike), map( many0(preceded( - opt(ignore_space), + opt(ignore_comments), alt(( - terminated( - alt(( - function_call, - dictionary, - map( - delimited(tag("("), media_args, tag(")")), - |v| Value::Paren(Box::new(v), true), - ), - map(sass_string, Value::Literal), - map(sass_string_dq, Value::Literal), - map(sass_string_sq, Value::Literal), - )), - alt(( - value((), all_consuming(tag(""))), - value((), peek(one_of(") \r\n\t{,;"))), - )), + function_call, + dictionary, + delimited( + tag("("), + map(media_relation, |v| { + Value::Paren(Box::new(v), true) + }), + tag(")"), ), - map(map_res(is_not("#()\"'{};, "), input_to_str), |s| { - Value::Literal(s.trim_end().into()) + bracket_list, + into(value::numeric), + variable, + map(sass_string, |s| { + Value::Literal({ + let lower = s + .single_raw() + .unwrap_or_default() + .to_lowercase(); + if lower == "not" || lower == "only" { + lower.into() + } else { + s + } + }) }), + map(sass_string_dq, Value::Literal), + map(sass_string_sq, Value::Literal), )), )), |args| { @@ -390,6 +401,31 @@ fn media_args(input: Span) -> PResult { )) } +fn media_relation(input: Span) -> PResult { + let (rest, first) = media_additive_expr(input)?; + if let Ok((rest, (op, b))) = tuple(( + delimited(opt_spacelike, relational_operator, opt_spacelike), + media_relation, + ))(rest) + { + let pos = input.up_to(&rest).to_owned(); + Ok(( + rest, + Value::BinOp(Box::new(BinOp::new(first, true, op, true, b, pos))), + )) + } else { + Ok((rest, first)) + } +} + +fn media_additive_expr(input: Span) -> PResult { + any_additive_expr(media_product, input) +} + +fn media_product(input: Span) -> PResult { + any_product(media_args, input) +} + #[cfg(test)] pub(crate) fn check_parse( parser: impl Fn(Span) -> PResult, diff --git a/rsass/src/parser/strings.rs b/rsass/src/parser/strings.rs index eb8612aec..0aac1bf16 100644 --- a/rsass/src/parser/strings.rs +++ b/rsass/src/parser/strings.rs @@ -366,7 +366,8 @@ fn selector_string(input: Span) -> PResult { fn selector_plain_part(input: Span) -> PResult<&str> { // TODO: This should include "%", but then it needs to be handled // specially for keyframes percentages and placeholder selectors. - map_res(is_not("\r\n\t >$\"'\\#+*/()[]{}:;,=!&@"), input_to_str)(input) + // Also TODO: This should probably be based on unicode alphanumeric. + map_res(is_not("\r\n\t <>$\"'\\#+*/()[]{}:;,=!&@"), input_to_str)(input) } fn hash_no_interpolation(input: Span) -> PResult<&str> { diff --git a/rsass/src/parser/value.rs b/rsass/src/parser/value.rs index d49f36efd..d34e6ab93 100644 --- a/rsass/src/parser/value.rs +++ b/rsass/src/parser/value.rs @@ -121,18 +121,7 @@ fn logic_expression(input: Span) -> PResult { let (input1, a) = sum_expression(input)?; fold_many0( tuple(( - delimited( - multispace0, - alt(( - value(Operator::Equal, tag("==")), - value(Operator::NotEqual, tag("!=")), - value(Operator::GreaterE, tag(">=")), - value(Operator::Greater, tag(">")), - value(Operator::LesserE, tag("<=")), - value(Operator::Lesser, tag("<")), - )), - multispace0, - ), + delimited(multispace0, relational_operator, multispace0), sum_expression, position, )), @@ -144,8 +133,26 @@ fn logic_expression(input: Span) -> PResult { )(input1) } +fn relational_operator(input: Span) -> PResult { + alt(( + value(Operator::Equal, tag("==")), + value(Operator::NotEqual, tag("!=")), + value(Operator::GreaterE, tag(">=")), + value(Operator::Greater, tag(">")), + value(Operator::LesserE, tag("<=")), + value(Operator::Lesser, tag("<")), + ))(input) +} + fn sum_expression(input: Span) -> PResult { - let (rest, v) = term_value(input)?; + any_additive_expr(term_value, input) +} + +pub fn any_additive_expr(term: F, input: Span) -> PResult +where + F: Fn(Span) -> PResult, +{ + let (rest, v) = term(input)?; fold_many0( tuple(( verify( @@ -159,7 +166,7 @@ fn sum_expression(input: Span) -> PResult { )), |(s1, op, s2)| op == &Operator::Plus || !*s1 || *s2, ), - term_value, + term, position, )), move || v.clone(), @@ -171,7 +178,14 @@ fn sum_expression(input: Span) -> PResult { } fn term_value(input: Span) -> PResult { - let (rest, v) = single_value(input)?; + any_product(single_value, input) +} + +pub fn any_product(factor: F, input: Span) -> PResult +where + F: Fn(Span) -> PResult, +{ + let (rest, v) = factor(input)?; fold_many0( tuple(( ignore_comments, @@ -184,7 +198,7 @@ fn term_value(input: Span) -> PResult { value(Operator::Modulo, tag("%")), )), ignore_comments, - single_value, + factor, position, )), move || v.clone(), @@ -299,7 +313,7 @@ fn unicode_range(input: Span) -> PResult { )) } -fn bracket_list(input: Span) -> PResult { +pub fn bracket_list(input: Span) -> PResult { let (input, content) = delimited(tag("["), opt(value_expression), tag("]"))(input)?; Ok(( @@ -498,7 +512,7 @@ pub fn dictionary_inner(input: Span) -> PResult { pair( sum_expression, preceded( - delimited(opt_spacelike, tag(":"), opt_spacelike), + delimited(ignore_comments, tag(":"), opt_spacelike), space_list, ), ), diff --git a/rsass/src/sass/functions/color/hsl.rs b/rsass/src/sass/functions/color/hsl.rs index 1a733d23a..81cd929ac 100644 --- a/rsass/src/sass/functions/color/hsl.rs +++ b/rsass/src/sass/functions/color/hsl.rs @@ -77,8 +77,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { .into()) } v @ Value::Numeric(..) => Ok(make_call("grayscale", vec![v])), - v if dbg!(is_special(dbg!(&v))) => - Ok(make_call("grayscale", vec![v])), + v if is_special(&v) => Ok(make_call("grayscale", vec![v])), v => Err(is_not(&v, "a color")).named(name!(color)), }); def_va!(f, saturate(kwargs), |s| { diff --git a/rsass/src/sass/string.rs b/rsass/src/sass/string.rs index bae9bc045..2010612d0 100644 --- a/rsass/src/sass/string.rs +++ b/rsass/src/sass/string.rs @@ -179,3 +179,11 @@ impl From<&str> for SassString { } } } +impl From for SassString { + fn from(value: String) -> Self { + SassString { + parts: vec![StringPart::Raw(value)], + quotes: Quotes::None, + } + } +} diff --git a/rsass/src/sass/value.rs b/rsass/src/sass/value.rs index 9a5c6f7c8..d907602e0 100644 --- a/rsass/src/sass/value.rs +++ b/rsass/src/sass/value.rs @@ -1,4 +1,5 @@ use super::{CallArgs, Function, Name, SassString}; +use crate::error::ResultPos; use crate::input::SourcePos; use crate::output::Format; use crate::value::{BadOp, ListSeparator, Number, Numeric, Operator, Rgba}; @@ -317,23 +318,43 @@ impl BinOp { scope: &ScopeRef, arithmetic: bool, ) -> Result { - Ok(if self.op == Operator::And { + if self.op == Operator::And { let a = self.a.do_evaluate(scope.clone(), true)?; - if a.is_true() { + Ok(if a.is_true() { self.b.do_evaluate(scope.clone(), true)? } else { a - } + }) } else if self.op == Operator::Or { let a = self.a.do_evaluate(scope.clone(), true)?; - if a.is_true() { + Ok(if a.is_true() { a } else { self.b.do_evaluate(scope.clone(), true)? - } + }) + } else if self.op.is_cmp() { + let aa = self.a.do_evaluate(scope.clone(), true)?; + let ba = self.b.do_evaluate(scope.clone(), true)?; + self.op + .eval(aa, ba) + .map_err(|e| match e { + BadOp::UndefinedOperation => Invalid::AtError(format!( + "Undefined operation \"{}\".", + Inspect(self) + )), + BadOp::Invalid(e) => Invalid::AtError(e.to_string()), + }) + .at(&self.pos)? + .ok_or(()) + .or_else(|()| { + let a = self.a.do_evaluate(scope.clone(), arithmetic)?; + let b = self.b.do_evaluate(scope.clone(), arithmetic)?; + Ok(css::BinOp::new(a, self.s1, self.op, self.s2, b) + .into()) + }) } else { let (a, b) = { - let arithmetic = arithmetic | (self.op != Operator::Div); + let arithmetic = arithmetic || (self.op != Operator::Div); let aa = self.a.do_evaluate(scope.clone(), arithmetic)?; let b = self.b.do_evaluate( scope.clone(), @@ -345,23 +366,26 @@ impl BinOp { (aa, b) } }; - self.op + Ok(self + .op .eval(a.clone(), b.clone()) .map_err(|e| match e { BadOp::UndefinedOperation => Invalid::AtError(format!( "Undefined operation \"{}\".", Inspect(self) - )) - .at(self.pos.clone()), - BadOp::Invalid(e) => { - Invalid::AtError(e.to_string()).at(self.pos.clone()) - } - })? + )), + BadOp::Invalid(e) => Invalid::AtError(e.to_string()), + }) + .at(&self.pos)? .unwrap_or_else(|| { - let sx = (self.op == Operator::Plus) - || ((self.op != Operator::Div) - && a.type_name() != "string" - && b.type_name() != "string"); + let sx = match self.op { + Operator::Div => false, + Operator::Minus => { + a.type_name() != "string" + && b.type_name() != "string" + } + _ => true, + }; css::BinOp::new( a, self.s1 && sx, @@ -370,8 +394,8 @@ impl BinOp { b, ) .into() - }) - }) + })) + } } } diff --git a/rsass/src/value/list_separator.rs b/rsass/src/value/list_separator.rs index dfd1fd8bb..daabaf509 100644 --- a/rsass/src/value/list_separator.rs +++ b/rsass/src/value/list_separator.rs @@ -6,6 +6,8 @@ pub enum ListSeparator { Space, /// The list is slash-separated. Slash, + /// The list is slash-separated without whitespace. + SlashNoSpace, /// The list is comma-separated. Comma, } @@ -17,6 +19,7 @@ impl ListSeparator { ListSeparator::Comma if compressed => ",", ListSeparator::Comma => ", ", ListSeparator::Slash if compressed => "/", + ListSeparator::SlashNoSpace => "/", ListSeparator::Slash => " / ", ListSeparator::Space => " ", } diff --git a/rsass/src/value/number.rs b/rsass/src/value/number.rs index 516c6e305..75046fa5a 100644 --- a/rsass/src/value/number.rs +++ b/rsass/src/value/number.rs @@ -877,10 +877,7 @@ fn debug_int_value() { assert_eq!( format!("{:#?}", crate::sass::Value::scalar(17)), "Numeric(\ - \n Numeric {\ - \n value: Number 17 / 1,\ - \n unit: UnitSet [],\ - \n },\ + \n Number 17 / 1; UnitSet [],\ \n)", ); } diff --git a/rsass/src/value/numeric.rs b/rsass/src/value/numeric.rs index 94aa27d65..ea2ddb327 100644 --- a/rsass/src/value/numeric.rs +++ b/rsass/src/value/numeric.rs @@ -5,7 +5,7 @@ use std::ops::{Div, Mul, Neg}; /// A Numeric value is a [`Number`] with a [`Unit`] (which may be /// Unit::None). -#[derive(Clone, Debug, Eq)] +#[derive(Clone, Eq)] pub struct Numeric { /// The number value of this numeric. pub value: Number, @@ -13,6 +13,12 @@ pub struct Numeric { pub unit: UnitSet, } +impl fmt::Debug for Numeric { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}; {:?}", self.value, self.unit) + } +} + impl Numeric { /// Create a new numeric value. /// diff --git a/rsass/src/value/operator.rs b/rsass/src/value/operator.rs index dc2a53881..b85c8a6b1 100644 --- a/rsass/src/value/operator.rs +++ b/rsass/src/value/operator.rs @@ -11,6 +11,8 @@ pub enum Operator { Or, /// The `==` operator. Equal, + /// The `=` operator. Used insted of Equal in media queries. + EqualSingle, /// The `!=` operator. NotEqual, /// The `>` operator. @@ -63,6 +65,7 @@ impl Operator { Operator::And => Some(if !a.is_true() { a } else { b }), Operator::Or => Some(if a.is_true() { a } else { b }), Operator::Equal => Some(Value::from(a == b)), + Operator::EqualSingle => cmp(a, b, &|a, b| a == b), Operator::NotEqual => Some(Value::from(a != b)), Operator::Greater => cmp(a, b, &|a, b| a > b), Operator::GreaterE => cmp(a, b, &|a, b| a >= b), @@ -183,6 +186,18 @@ impl Operator { Operator::Not => return Err(BadOp::UndefinedOperation), }) } + pub(crate) fn is_cmp(&self) -> bool { + matches!( + self, + Operator::Equal + | Operator::EqualSingle + | Operator::NotEqual + | Operator::Greater + | Operator::GreaterE + | Operator::Lesser + | Operator::LesserE + ) + } } fn valid_operand(v: &Value) -> bool { @@ -213,6 +228,7 @@ impl fmt::Display for Operator { Operator::And => "and", Operator::Or => "or", Operator::Equal => "==", + Operator::EqualSingle => "=", Operator::NotEqual => "!=", Operator::Greater => ">", Operator::GreaterE => ">=", diff --git a/rsass/tests/spec/css/media/logic/and.rs b/rsass/tests/spec/css/media/logic/and.rs index 36e12f682..549acda12 100644 --- a/rsass/tests/spec/css/media/logic/and.rs +++ b/rsass/tests/spec/css/media/logic/and.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn comment_after() { assert_eq!( runner().ok("@media (a) and/**/(b) {x {y: z}}\n"), @@ -29,7 +28,6 @@ fn interpolation() { ); } #[test] -#[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media (a) AnD (b) {x {y: z}}\n"), @@ -52,7 +50,6 @@ fn multiple() { ); } #[test] -#[ignore] // unexepected error fn no_whitespace_before() { assert_eq!( runner().ok("@media (a)and (b) {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/logic/and_not.rs b/rsass/tests/spec/css/media/logic/and_not.rs index 194033780..553d3b224 100644 --- a/rsass/tests/spec/css/media/logic/and_not.rs +++ b/rsass/tests/spec/css/media/logic/and_not.rs @@ -43,7 +43,6 @@ mod comment_after { use super::runner; #[test] - #[ignore] // wrong result fn after_type() { assert_eq!( runner().ok("@media a and not/**/(b) {x {y: z}}\n"), @@ -55,7 +54,6 @@ mod comment_after { ); } #[test] - #[ignore] // wrong result fn after_type_and_modifier() { assert_eq!( runner().ok("@media only a and not/**/(b) {x {y: z}}\n"), @@ -79,7 +77,6 @@ fn interpolation() { ); } #[test] -#[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media a AnD nOt (b) {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/logic/error.rs b/rsass/tests/spec/css/media/logic/error.rs index afc277451..220553856 100644 --- a/rsass/tests/spec/css/media/logic/error.rs +++ b/rsass/tests/spec/css/media/logic/error.rs @@ -169,7 +169,7 @@ mod nothing_after { use super::runner; #[test] - #[ignore] // missing error + #[ignore] // wrong error fn after_interpolation() { assert_eq!( runner().err("@media #{a} and {x {y: z}}\n"), @@ -182,7 +182,7 @@ mod nothing_after { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn after_paren() { assert_eq!( runner().err("@media (a) and {x {y: z}}\n"), @@ -195,7 +195,7 @@ mod nothing_after { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn after_type() { assert_eq!( runner().err("@media a and {x {y: z}}\n"), @@ -235,7 +235,7 @@ mod nothing_after { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn or() { assert_eq!( runner().err("@media (a) or {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/logic/nested.rs b/rsass/tests/spec/css/media/logic/nested.rs index 1b3ecbf70..51a6ebe2f 100644 --- a/rsass/tests/spec/css/media/logic/nested.rs +++ b/rsass/tests/spec/css/media/logic/nested.rs @@ -25,6 +25,7 @@ mod interpolated { ); } #[test] + #[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media (#{\"(a) AnD (b)\"}) {x {y: z}}\n"), @@ -80,6 +81,7 @@ mod interpolated { ); } #[test] + #[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media (#{\"(a) oR (b)\"}) {x {y: z}}\n"), @@ -112,7 +114,6 @@ mod raw { ); } #[test] - #[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media ((a) AnD (b)) {x {y: z}}\n"), @@ -180,7 +181,6 @@ mod raw { ); } #[test] - #[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media ((a) Or (b)) {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/logic/not.rs b/rsass/tests/spec/css/media/logic/not.rs index 4da73d187..1dfde83aa 100644 --- a/rsass/tests/spec/css/media/logic/not.rs +++ b/rsass/tests/spec/css/media/logic/not.rs @@ -10,7 +10,6 @@ mod not { use super::runner; #[test] - #[ignore] // wrong result fn comment_after() { assert_eq!( runner().ok("@media not/**/(a) {x {y: z}}\n"), @@ -33,7 +32,6 @@ mod not { ); } #[test] - #[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media NoT (a) {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/logic/or.rs b/rsass/tests/spec/css/media/logic/or.rs index d4ede79aa..e01765629 100644 --- a/rsass/tests/spec/css/media/logic/or.rs +++ b/rsass/tests/spec/css/media/logic/or.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn comment_after() { assert_eq!( runner().ok("@media (a) or/**/(b) {x {y: z}}\n"), @@ -29,7 +28,6 @@ fn interpolation() { ); } #[test] -#[ignore] // wrong result fn mixed_case() { assert_eq!( runner().ok("@media (a) oR (b) {x {y: z}}\n"), @@ -52,7 +50,6 @@ fn multiple() { ); } #[test] -#[ignore] // unexepected error fn no_whitespace_before() { assert_eq!( runner().ok("@media (a)or (b) {x {y: z}}\n"), diff --git a/rsass/tests/spec/css/media/range/error.rs b/rsass/tests/spec/css/media/range/error.rs index e60b86624..bc958663d 100644 --- a/rsass/tests/spec/css/media/range/error.rs +++ b/rsass/tests/spec/css/media/range/error.rs @@ -10,7 +10,7 @@ mod invalid_binary_operator { use super::runner; #[test] - #[ignore] // missing error + #[ignore] // wrong error fn before_colon() { assert_eq!( runner().err( @@ -67,7 +67,7 @@ mod invalid_binary_operator { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn in_subexpression() { assert_eq!( runner().err( @@ -116,7 +116,7 @@ mod invalid_comparison { use super::runner; #[test] - #[ignore] // missing error + #[ignore] // wrong error fn gte() { assert_eq!( runner().err("@media (width > = 100px) {a {b: c}}\n"), @@ -129,7 +129,7 @@ mod invalid_comparison { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn lte() { assert_eq!( runner().err("@media (width < = 100px) {a {b: c}}\n"), @@ -142,7 +142,7 @@ mod invalid_comparison { ); } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn range_gte() { assert_eq!( runner().err("@media (10px > width > = 1px) {a {b: c}}\n"), diff --git a/rsass/tests/spec/css/media/range/with_expressions.rs b/rsass/tests/spec/css/media/range/with_expressions.rs index f4cb2aba4..6c20bbd80 100644 --- a/rsass/tests/spec/css/media/range/with_expressions.rs +++ b/rsass/tests/spec/css/media/range/with_expressions.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok( diff --git a/rsass/tests/spec/css/plain/error/media.rs b/rsass/tests/spec/css/plain/error/media.rs index 3f7b6f0b4..23f3671d6 100644 --- a/rsass/tests/spec/css/plain/error/media.rs +++ b/rsass/tests/spec/css/plain/error/media.rs @@ -295,7 +295,7 @@ mod missing_whitespace { } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn after_type() { let runner = runner().with_cwd("after_type"); assert_eq!( @@ -310,7 +310,7 @@ mod missing_whitespace { ); } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn first() { let runner = runner().with_cwd("first"); assert_eq!( @@ -325,7 +325,7 @@ mod missing_whitespace { ); } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn later() { let runner = runner().with_cwd("later"); assert_eq!( @@ -347,7 +347,7 @@ mod missing_whitespace { } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn test_type() { let runner = runner().with_cwd("type"); assert_eq!( @@ -362,7 +362,7 @@ mod missing_whitespace { ); } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn type_and_modifier() { let runner = runner().with_cwd("type_and_modifier"); assert_eq!( @@ -378,7 +378,7 @@ mod missing_whitespace { } } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn not() { let runner = runner().with_cwd("not"); assert_eq!( @@ -399,7 +399,7 @@ mod missing_whitespace { } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn first() { let runner = runner().with_cwd("first"); assert_eq!( @@ -414,7 +414,7 @@ mod missing_whitespace { ); } #[test] - #[ignore] // wrong error + #[ignore] // missing error fn later() { let runner = runner().with_cwd("later"); assert_eq!( diff --git a/rsass/tests/spec/css/plain/media.rs b/rsass/tests/spec/css/plain/media.rs index 82fa513e3..e2f2faed4 100644 --- a/rsass/tests/spec/css/plain/media.rs +++ b/rsass/tests/spec/css/plain/media.rs @@ -147,6 +147,7 @@ mod logic { ); } #[test] + #[ignore] // wrong result fn mixed_case() { let runner = runner().with_cwd("mixed_case"); assert_eq!( @@ -178,6 +179,7 @@ mod logic { ); } #[test] + #[ignore] // wrong result fn mixed_case() { let runner = runner().with_cwd("mixed_case"); assert_eq!( diff --git a/rsass/tests/spec/css/plain/slash.rs b/rsass/tests/spec/css/plain/slash.rs index 95f007449..8b6579125 100644 --- a/rsass/tests/spec/css/plain/slash.rs +++ b/rsass/tests/spec/css/plain/slash.rs @@ -8,7 +8,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("@import \"plain\";\n"), diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1218.rs b/rsass/tests/spec/libsass_closed_issues/issue_1218.rs index ebee0fa81..92bc2f5d9 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1218.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1218.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result +#[ignore] // unexepected error fn test() { assert_eq!( runner().ok("$foo: 20px;\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1557.rs b/rsass/tests/spec/libsass_closed_issues/issue_1557.rs index f671b4ab0..34798f245 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1557.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1557.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1793.rs b/rsass/tests/spec/libsass_closed_issues/issue_1793.rs index bbbb613a3..28523feae 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1793.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1793.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1794.rs b/rsass/tests/spec/libsass_closed_issues/issue_1794.rs index 0635e762d..6d10acde8 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1794.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1794.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("@media (max-width /*comment*/ : 500px) {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2095.rs b/rsass/tests/spec/libsass_closed_issues/issue_2095.rs index 9da99a874..c72ba9a31 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2095.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2095.rs @@ -8,7 +8,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("@media all {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2154.rs b/rsass/tests/spec/libsass_closed_issues/issue_2154.rs index 41e25d9a0..efcbe2223 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2154.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2154.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("@media (min-width: 1px) {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_803.rs b/rsass/tests/spec/libsass_closed_issues/issue_803.rs index 35b75e589..0895add75 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_803.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_803.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok(