diff --git a/Cargo.toml b/Cargo.toml index 5cb3b3378082..33a6e052ddc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ name = "swc" [dependencies] swc_atoms = { path ="./atoms" } swc_common = { path ="./common", features = ["sourcemap", "concurrent"] } +swc_css = { path ="./css" } swc_ecma_ast = { path ="./ecmascript/ast" } swc_ecma_codegen = { path ="./ecmascript/codegen" } swc_ecma_parser = { path ="./ecmascript/parser" } diff --git a/atoms/Cargo.toml b/atoms/Cargo.toml index cc4e46ef2cd3..008a7338fd69 100644 --- a/atoms/Cargo.toml +++ b/atoms/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "swc_atoms" build = "build.rs" -version = "0.2.2" +version = "0.2.3" authors = ["강동윤 "] license = "Apache-2.0/MIT" repository = "https://github.com/swc-project/swc.git" @@ -13,4 +13,4 @@ edition = "2018" string_cache = "0.8" [build-dependencies] -string_cache_codegen = "0.5" \ No newline at end of file +string_cache_codegen = "0.5" diff --git a/atoms/words.txt b/atoms/words.txt index 32dc006c50ab..37a0979001b7 100644 --- a/atoms/words.txt +++ b/atoms/words.txt @@ -573,6 +573,7 @@ _defineProperty _extends _toConsumableArray abstract +and any apply arguments @@ -586,6 +587,7 @@ break call case catch +charset class concat const @@ -607,6 +609,7 @@ export extends false finally +font-face for from function @@ -615,6 +618,7 @@ global if implements import +important in infer instanceof @@ -622,9 +626,11 @@ interface is iterator key +keyframes keyof length let +media meta module namespace @@ -634,6 +640,7 @@ null number object of +or package private process @@ -646,6 +653,7 @@ set static string super +supports switch symbol target diff --git a/css/Cargo.toml b/css/Cargo.toml new file mode 100644 index 000000000000..c73409f797cc --- /dev/null +++ b/css/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["강동윤 "] +edition = "2018" +name = "swc_css" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +swc_css_ast = {path = "./ast"} +swc_css_parser = {path = "./parser"} diff --git a/css/ast/Cargo.toml b/css/ast/Cargo.toml new file mode 100644 index 000000000000..3d7c43e730d0 --- /dev/null +++ b/css/ast/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "swc_css_ast" +version = "0.1.0" +authors = ["강동윤 "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +swc_atoms = { path = "../../atoms" } +swc_common = { path = "../../common" } +swc_visit = { path = "../../visit" } +serde = { version = "1", features = ["derive"] } \ No newline at end of file diff --git a/css/ast/src/at_rule.rs b/css/ast/src/at_rule.rs new file mode 100644 index 000000000000..6d554f30e604 --- /dev/null +++ b/css/ast/src/at_rule.rs @@ -0,0 +1,120 @@ +use crate::{media_query::MediaQuery, DeclBlock, Number, ParenProperty, Str, Text}; +use swc_common::{ast_node, Span}; +#[ast_node] +pub enum AtRule { + #[tag("CharsetRule")] + Charset(CharsetRule), + #[tag("MediaRule")] + Media(MediaRule), + #[tag("ImportRule")] + Import(ImportRule), + #[tag("SupportsRule")] + Supports(SupportsRule), + #[tag("KeyframesRule")] + Keyframes(KeyframesRule), + #[tag("FontFaceRule")] + FontFace(FontFaceRule), + #[tag("*")] + Unknown(UnknownAtRule), +} + +#[ast_node] +pub struct CharsetRule { + pub span: Span, + pub charset: Str, +} + +#[ast_node] +pub struct MediaRule { + pub span: Span, + pub query: Box, +} + +#[ast_node] +pub struct ImportRule { + pub span: Span, + pub src: Str, +} + +#[ast_node] +pub struct PageRule { + pub span: Span, + /// TODO: Create a dedicated ast type. + pub selector: Option, +} + +#[ast_node] +pub struct SupportsRule { + pub span: Span, + /// TODO: Create a dedicated ast type. + pub query: Box, + + pub rules: Vec, +} + +#[ast_node] +pub enum SupportsQuery { + #[tag("Property")] + Property(ParenProperty), + + #[tag("AndSupportsQuery")] + And(AndSupportsQuery), + + #[tag("OrSupportsQuery")] + Or(OrSupportsQuery), +} + +#[ast_node] +pub struct AndSupportsQuery { + pub span: Span, + pub first: Box, + pub second: Box, +} + +#[ast_node] +pub struct OrSupportsQuery { + pub span: Span, + pub first: Box, + pub second: Box, +} + +#[ast_node] +pub struct KeyframesRule { + pub span: Span, + pub name: Text, + pub keyframes: Vec, +} + +#[ast_node] +pub struct KeyframeElement { + pub span: Span, + pub selector: KeyframeSelector, + pub block: DeclBlock, +} + +#[ast_node] +pub enum KeyframeSelector { + #[tag("KeyframePercentSelector")] + Percent(KeyframePercentSelector), + #[tag("Text")] + Extra(Text), +} + +#[ast_node] +pub struct KeyframePercentSelector { + pub span: Span, + pub percent: Number, +} + +#[ast_node] +pub struct FontFaceRule { + pub span: Span, + pub block: DeclBlock, +} + +#[ast_node] +pub struct UnknownAtRule { + pub span: Span, + pub name: Text, + pub extras: Text, +} diff --git a/css/ast/src/lib.rs b/css/ast/src/lib.rs new file mode 100644 index 000000000000..88afe877d10d --- /dev/null +++ b/css/ast/src/lib.rs @@ -0,0 +1,323 @@ +#![deny(unused)] + +pub use self::{at_rule::*, media_query::*}; +pub use at_rule::AtRule; +use serde::{Deserialize, Serialize}; +use swc_atoms::JsWord; +use swc_common::{ast_node, Span}; + +mod at_rule; +mod media_query; + +/// Quoted string +#[ast_node] +pub struct Str { + /// Includes quotes + pub span: Span, + /// Does not include quotes + pub sym: String, +} + +#[ast_node] +pub struct Text { + pub span: Span, + pub sym: JsWord, +} + +#[ast_node] +pub struct Number { + pub span: Span, + pub value: Box, +} + +#[ast_node] +pub struct Stylesheet { + pub span: Span, + pub rules: Vec, +} + +#[ast_node] +pub enum Rule { + #[tag("AtRule")] + At(AtRule), + #[tag("StyleRule")] + Style(StyleRule), +} + +#[ast_node] +pub struct StyleRule { + pub span: Span, + pub selectors: Vec>, + pub block: DeclBlock, +} + +#[ast_node] +pub struct DeclBlock { + /// Includes `{` and `}`. + pub span: Span, + pub properties: Vec, +} + +/// A selector is composed of `CompoundSelector`s separated by +/// `Combinator`s. It selects elements based on their parent selectors. +#[ast_node] +pub struct Selector { + pub span: Span, + /// This is never empty. + pub components: Vec, +} + +#[ast_node] +pub enum SimpleSelector { + /// `*` + #[tag("UniversalSelector")] + Universal(UniversalSelector), + + /// A pseudo-class or pseudo-element selector. + /// + /// The semantics of a specific pseudo selector depends on its name. Some + /// selectors take arguments, including other selectors. + #[tag("PseudoSelector")] + Pseudo(PseudoSelector), + + /// A type selector. + /// + /// This selects elements whose name equals the given name. + #[tag("TagSelector")] + Tag(TagSelector), + #[tag("IdSelector")] + Id(IdSelector), + + /// A class selector. + /// + /// This selects elements whose `class` attribute contains an identifier + /// with the given name. + #[tag("ClassSelector")] + Class(ClassSelector), + + #[tag("AttributeSelector")] + Attribute(AttributeSelector), +} + +#[ast_node] +pub struct UniversalSelector { + pub span: Span, +} + +#[ast_node] +pub struct AttributeSelector { + pub span: Span, + pub attr: Text, + /// TODO: Change this to something better. + pub value: Option, + pub modifier: Option, + pub op: AttributeOp, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum AttributeOp { + /// \[attr\] + /// + /// Represents elements with an attribute name of `attr` + Any, + + /// [attr=value] + /// + /// Represents elements with an attribute name of `attr` + /// whose value is exactly `value` + Equals, + + /// [attr~=value] + /// + /// Represents elements with an attribute name of `attr` + /// whose value is a whitespace-separated list of words, + /// one of which is exactly `value` + Include, + + /// [attr|=value] + /// + /// Represents elements with an attribute name of `attr` + /// whose value can be exactly value or can begin with + /// `value` immediately followed by a hyphen (`-`) + Dash, + + /// [attr^=value] + Prefix, + + /// [attr$=value] + Suffix, + + /// [attr*=value] + /// + /// Represents elements with an attribute name of `attr` + /// whose value contains at least one occurrence of + /// `value` within the string + Contains, +} + +/// e.g. `a.my-btn` +#[ast_node] +pub struct CompoundSelector { + pub span: Span, + pub selectors: Vec, +} + +#[ast_node] +pub enum SelectorComponent { + #[tag("CompoundSelector")] + Compound(CompoundSelector), + #[tag("CombinatorSelector")] + Combinator(CombinatorSelector), +} + +#[ast_node] +pub struct CombinatorSelector { + pub span: Span, + pub combinator: Combinator, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Combinator { + /// Matches the right-hand selector if it's immediately adjacent to the + /// left-hand selector in the DOM tree. + /// + /// `'+'` + NextSibling, + + /// Matches the right-hand selector if it's a direct child of the left-hand + /// selector in the DOM tree. + /// + /// `'>'` + Child, + + /// Matches the right-hand selector if it comes after the left-hand selector + /// in the DOM tree. + /// + /// `'~'` + FollowingSibling, +} + +#[ast_node] +pub struct PseudoSelector { + pub span: Span, + + /// The name of this selector. + pub name: Text, + + /// Whether this is a pseudo-class selector. + /// + /// If this is false, this is a pseudo-element selector + pub is_class: bool, + + /// Whether this is syntactically a pseudo-class selector. + /// + /// This is the same as `is_class` unless this selector is a pseudo-element + /// that was written syntactically as a pseudo-class (`:before`, `:after`, + /// `:first-line`, or `:first-letter`). + /// + /// If this is false, it is syntactically a psuedo-element + pub is_syntactic_class: bool, + + /// The non-selector argument passed to this selector. + /// + /// This is `None` if there's no argument. If `argument` and `selector` are + /// both non-`None`, the selector follows the argument. + pub arguments: Option>, + + /// The selector argument passed to this selector. + /// + /// This is `None` if there's no selector. If `argument` and `selector` are + /// both non-`None`, the selector follows the argument. + pub selector: Option>>, +} + +#[ast_node] +pub struct IdSelector { + pub span: Span, + /// Does not include `#` + pub text: Text, +} + +#[ast_node] +pub struct ClassSelector { + pub span: Span, + /// Does not include `.` + pub text: Text, +} + +#[ast_node] +pub struct TagSelector { + pub span: Span, + pub text: Text, +} + +#[ast_node] +pub enum BaseSelector { + #[tag("IdSelector")] + Id(IdSelector), + #[tag("ClassSelector")] + Class(ClassSelector), +} + +#[ast_node] +pub struct Property { + pub span: Span, + pub name: Text, + pub value: Value, + /// The span includes `!` + pub important: Option, +} + +#[ast_node] +pub enum Value { + #[tag("ParenValue")] + Paren(ParenValue), + #[tag("UnitValue")] + Unit(UnitValue), + #[tag("Number")] + Number(Number), + #[tag("HashValue")] + Hash(HashValue), + #[tag("Text")] + Text(Text), + #[tag("Str")] + Str(Str), +} + +#[ast_node] +pub struct ParenProperty { + /// Includes `(` and `)` + pub span: Span, + pub property: Property, +} + +#[ast_node] +pub struct HashValue { + /// Includes `#` + pub span: Span, + /// Does **not** include `#` + pub value: Text, +} + +#[ast_node] +pub struct UnitValue { + pub span: Span, + pub value: Text, + pub unit: SpannedUnit, +} + +#[ast_node] +pub struct SpannedUnit { + pub span: Span, + pub unit: Unit, +} +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Unit { + Px, +} + +#[ast_node] +pub struct ParenValue { + pub span: Span, + pub value: Box, +} diff --git a/css/ast/src/media_query.rs b/css/ast/src/media_query.rs new file mode 100644 index 000000000000..32f8b51ac283 --- /dev/null +++ b/css/ast/src/media_query.rs @@ -0,0 +1,29 @@ +use crate::{ParenProperty, Text}; +use swc_common::{ast_node, Span}; + +#[ast_node] +pub enum MediaQuery { + #[tag("AndMediaQuery")] + And(AndMediaQuery), + #[tag("OrMediaQuery")] + Or(OrMediaQuery), + /// e.g. `screen` + #[tag("Text")] + Text(Text), + #[tag("ParenPropery")] + Value(ParenProperty), +} + +#[ast_node] +pub struct AndMediaQuery { + pub span: Span, + pub first: Box, + pub second: Box, +} + +#[ast_node] +pub struct OrMediaQuery { + pub span: Span, + pub first: Box, + pub second: Box, +} diff --git a/css/parser/Cargo.toml b/css/parser/Cargo.toml new file mode 100644 index 000000000000..e7b76ad3a4c4 --- /dev/null +++ b/css/parser/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ["강동윤 "] +edition = "2018" +name = "swc_css_parser" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +logos = "0.11.4" +matches = "0.1.8" +serde = {version = "1", features = ["derive"]} +swc_atoms = {path = "../../atoms"} +swc_common = {path = "../../common"} +swc_css_ast = {path = "../ast"} + +[dev-dependencies] +paste = "1.0.1" +serde_json = "1" +testing = {path = "../../testing"} +walkdir = "2" diff --git a/css/parser/README.md b/css/parser/README.md new file mode 100644 index 000000000000..427cf896e3bd --- /dev/null +++ b/css/parser/README.md @@ -0,0 +1,6 @@ +# swc_css_parser + +## Notes + +We use test suite from grass for testing. +We verify that **output** of grass can be parsed by this crate. diff --git a/css/parser/src/error.rs b/css/parser/src/error.rs new file mode 100644 index 000000000000..37935062820b --- /dev/null +++ b/css/parser/src/error.rs @@ -0,0 +1,30 @@ +use swc_common::{Span, Spanned}; + +#[derive(Debug, Clone)] +pub struct Error { + pub(crate) inner: Box<(Span, SyntaxError)>, +} + +impl Spanned for Error { + #[cold] + fn span(&self) -> Span { + self.inner.0 + } +} + +impl Error { + #[cold] + pub fn into_kind(self) -> SyntaxError { + self.inner.1 + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum SyntaxError { + Eof, + ExpectedWord { got: String }, + ExpectedStr { got: String }, + Expected { expected: String, got: String }, + ExpectedOneOf { expected: String, got: String }, +} diff --git a/css/parser/src/lexer.rs b/css/parser/src/lexer.rs new file mode 100644 index 000000000000..520bf7ea4d8c --- /dev/null +++ b/css/parser/src/lexer.rs @@ -0,0 +1,101 @@ +use crate::token::Token; +use logos::Logos; +use swc_common::{BytePos, Span}; + +/// Wrapper for +pub(crate) struct Lexer<'i> { + inner: logos::Lexer<'i, Token>, + start_pos: BytePos, + prev_hi: BytePos, + /// Does a line break exist before `cur`? + had_ws: bool, + cur: Option<(Token, logos::Span, &'i str)>, +} + +impl<'i> Lexer<'i> { + pub fn had_ws_before_cur(&mut self) -> bool { + let _ = self.cur(); + + self.had_ws + } + + #[inline] + pub fn bump(&mut self) { + let _ = self.cur(); + + self.cur.take(); + + self.prev_hi = self.prev_hi + BytePos(self.inner.slice().len() as _); + } + + #[inline] + pub fn new(start_pos: BytePos, s: &'i str) -> Self { + Lexer { + start_pos, + cur: None, + inner: Token::lexer(s), + prev_hi: start_pos, + had_ws: false, + } + } + + #[inline] + pub fn cur_pos(&mut self) -> BytePos { + let _ = self.cur(); + + let span = self.span(); + + span.lo + } + + #[inline] + pub fn span(&mut self) -> Span { + let _ = self.cur(); + + let s = match self.cur.as_ref() { + Some(v) => v.1.clone(), + _ => return Span::new(self.prev_hi, self.prev_hi, Default::default()), + }; + + Span::new( + BytePos(s.start as _), + BytePos(s.end as _), + Default::default(), + ) + } + + pub const fn prev_hi(&self) -> BytePos { + self.prev_hi + } + + #[inline] + pub fn cur(&mut self) -> Option { + match self.cur { + Some(_) => {} + None => { + self.prev_hi = self.start_pos + BytePos(self.inner.span().end as u32); + let token = self.inner.next()?; + self.had_ws = self.inner.extras.had_whitespace; + self.inner.extras = Default::default(); + self.cur = Some((token, self.inner.span(), self.inner.slice())); + } + } + + self.cur.as_ref().map(|v| v.0) + } + + #[inline] + pub fn slice(&mut self) -> &'i str { + let _cur = self.cur(); + + match self.cur { + Some(ref v) => v.2, + None => "", + } + } + + #[inline] + pub fn make_span(&self, start: BytePos) -> Span { + Span::new(start, self.prev_hi, Default::default()) + } +} diff --git a/css/parser/src/lib.rs b/css/parser/src/lib.rs new file mode 100644 index 000000000000..aaf197213da9 --- /dev/null +++ b/css/parser/src/lib.rs @@ -0,0 +1,19 @@ +pub use self::error::{Error, SyntaxError}; +use lexer::Lexer; +pub use parser::{PResult, Parser}; +use swc_common::BytePos; +use swc_css_ast::Stylesheet; + +#[macro_use] +mod macros; +mod error; +mod lexer; +mod parser; +mod token; + +pub fn parse(start_pos: BytePos, src: &str) -> PResult { + let lexer = Lexer::new(start_pos, src); + let mut parser = Parser::new(lexer); + + parser.parse() +} diff --git a/css/parser/src/macros.rs b/css/parser/src/macros.rs new file mode 100644 index 000000000000..0478342bca9b --- /dev/null +++ b/css/parser/src/macros.rs @@ -0,0 +1,160 @@ +macro_rules! tok { + (";") => { + crate::token::Token::Semi + }; + ("{") => { + crate::token::Token::LBrace + }; + ("}") => { + crate::token::Token::RBrace + }; + ("(") => { + crate::token::Token::LParen + }; + (")") => { + crate::token::Token::RParen + }; + (",") => { + crate::token::Token::Comma + }; + (":") => { + crate::token::Token::Colon + }; + ("!important") => { + crate::token::Token::BangImportant + }; + (".") => { + crate::token::Token::Dot + }; + ("*") => { + crate::token::Token::Mul + }; + ("#") => { + crate::token::Token::Hash + }; + ("[") => { + crate::token::Token::LBracket + }; + ("]") => { + crate::token::Token::RBracket + }; + ("<") => { + crate::token::Token::Lt + }; + (">") => { + crate::token::Token::Gt + }; + ("=") => { + crate::token::Token::Eq + }; + ("$=") => { + crate::token::Token::DollarEq + }; + ("*=") => { + crate::token::Token::MulEq + }; + ("^=") => { + crate::token::Token::XorEq + }; + ("|=") => { + crate::token::Token::OrEq + }; + ("~=") => { + crate::token::Token::TildeEq + }; + ("+") => { + crate::token::Token::Plus + }; + ("~") => { + crate::token::Token::Tilde + }; +} + +macro_rules! cur { + ($parser:expr) => {{ + use crate::error::{Error, SyntaxError}; + + match $parser.i.cur() { + Some(token) => token, + None => Err(Error { + inner: Box::new(($parser.i.span(), SyntaxError::Eof)), + })?, + } + }}; +} + +macro_rules! expect { + ($parser:expr, $t:tt) => {{ + use crate::error::{Error, SyntaxError}; + + match $parser.i.cur() { + Some(tok!($t)) => { + $parser.i.bump(); + } + Some(other) => Err(Error { + inner: Box::new(( + $parser.i.span(), + SyntaxError::Expected { + expected: format!("{:?}", tok!($t)), + got: format!("{:?}", other), + }, + )), + })?, + None => Err(Error { + inner: Box::new(($parser.i.span(), SyntaxError::Eof)), + })?, + } + }}; +} + +macro_rules! is { + ($parser:expr, $t:tt) => {{ + match $parser.i.cur() { + Some(tok!($t)) => true, + _ => false, + } + }}; +} + +macro_rules! is_one_of { + ($parser:expr, $($t:tt),*) => {{ + match $parser.i.cur() { + $( + Some(tok!($t)) => true, + )* + _ => false, + } + }}; +} + +macro_rules! eat { + ($parser:expr, $t:tt) => {{ + if is!($parser, $t) { + $parser.i.bump(); + true + } else { + false + } + }}; +} + +macro_rules! trace_cur { + ($parser:expr, $name:ident) => {{ + // eprintln!("{}: {:?}", stringify!($name), cur!($parser)); + }}; +} + +/// Return an error +macro_rules! expected_one_of { + ($parser:expr, $($t:tt),*) => {{ + Err(crate::error::Error { + inner: Box::new(( + $parser.i.span(), + crate::error::SyntaxError::Expected { + expected: concat!( $($t, )* ).to_string(), + got: format!("{:?}", cur!($parser)), + }, + )), + })? + }}; +} diff --git a/css/parser/src/parser/at_rule.rs b/css/parser/src/parser/at_rule.rs new file mode 100644 index 000000000000..77c67003a890 --- /dev/null +++ b/css/parser/src/parser/at_rule.rs @@ -0,0 +1,281 @@ +use crate::{token::Token, PResult, Parser, SyntaxError}; +use swc_atoms::js_word; +use swc_common::BytePos; +use swc_css_ast::*; + +impl Parser<'_> { + pub(super) fn parse_at_rule(&mut self) -> PResult { + trace_cur!(self, parse_at_rule); + + assert_eq!(self.i.cur(), Some(Token::At)); + let start = self.i.cur_pos(); + self.i.bump(); // '@' + + let name = self.parse_word()?; + + match name.sym { + js_word!("charset") => self.parse_charset_rule(start).map(From::from), + js_word!("media") => self.parse_media_rule(start).map(From::from), + js_word!("import") => self.parse_import_rule(start).map(From::from), + js_word!("supports") => self.parse_supports_rule(start).map(From::from), + js_word!("keyframes") => self.parse_keyframes_rule(start).map(From::from), + js_word!("font-face") => self.parse_font_face_rule(start).map(From::from), + + _ => self.parse_unknown_at_rule(start).map(From::from), + } + } + + fn parse_charset_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_charset_rule); + + let charset = self.parse_str()?; + + Ok(CharsetRule { + span: self.i.make_span(start), + charset, + }) + } + + fn parse_media_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_media_rule); + + let query = self.parse_media_query()?; + + Ok(MediaRule { + span: self.i.make_span(start), + query, + }) + } + + fn parse_media_query(&mut self) -> PResult> { + trace_cur!(self, parse_media_query); + + let start = self.i.cur_pos(); + let mut query = self.parse_one_media_query_element()?; + + loop { + if is!(self, "{") { + break; + } + + let and_or = self.parse_word()?; + + match and_or.sym { + js_word!("and") => { + let q = self.parse_one_media_query_element()?; + query = Box::new(MediaQuery::And(AndMediaQuery { + span: self.i.make_span(start), + first: query, + second: q, + })) + } + js_word!("or") => { + let q = self.parse_one_media_query_element()?; + query = Box::new(MediaQuery::Or(OrMediaQuery { + span: self.i.make_span(start), + first: query, + second: q, + })) + } + _ => self.err(SyntaxError::ExpectedOneOf { + expected: "'and' or 'or'".into(), + got: String::from(&*and_or.sym), + })?, + } + } + + Ok(query) + } + + fn parse_one_media_query_element(&mut self) -> PResult> { + trace_cur!(self, parse_one_media_query_element); + + let start = self.i.cur_pos(); + + if eat!(self, "(") { + let property = self.parse_property()?; + expect!(self, ")"); + + return Ok(Box::new(MediaQuery::Value(ParenProperty { + span: self.i.make_span(start), + property, + }))); + } + + let text = self.parse_word()?; + + Ok(Box::new(MediaQuery::Text(text))) + } + + fn parse_import_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_import_rule); + + let src = self.parse_str()?; + + Ok(ImportRule { + span: self.i.make_span(start), + src, + }) + } + + fn parse_supports_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_supports_rule); + + expect!(self, "("); + let query = self.parse_supports_query()?; + expect!(self, ")"); + + expect!(self, "{"); + let mut rules = vec![]; + while is!(self, "{") { + let block = self.parse_decl_block()?; + rules.push(block); + } + expect!(self, "}"); + + Ok(SupportsRule { + span: self.i.make_span(start), + query, + rules, + }) + } + + fn parse_supports_query(&mut self) -> PResult> { + trace_cur!(self, parse_supports_query); + + let start = self.i.cur_pos(); + let mut query = self.parse_one_support_query_element()?; + + loop { + if is!(self, "{") { + break; + } + + let and_or = self.parse_word()?; + + match and_or.sym { + js_word!("and") => { + let q = self.parse_one_support_query_element()?; + query = Box::new(SupportsQuery::And(AndSupportsQuery { + span: self.i.make_span(start), + first: query, + second: q, + })) + } + js_word!("or") => { + let q = self.parse_one_support_query_element()?; + query = Box::new(SupportsQuery::Or(OrSupportsQuery { + span: self.i.make_span(start), + first: query, + second: q, + })) + } + _ => self.err(SyntaxError::ExpectedOneOf { + expected: "'and' or 'or'".into(), + got: String::from(&*and_or.sym), + })?, + } + } + + Ok(query) + } + + fn parse_one_support_query_element(&mut self) -> PResult> { + trace_cur!(self, parse_one_support_query_element); + + let start = self.i.cur_pos(); + if eat!(self, "(") { + let property = self.parse_property()?; + expect!(self, ")"); + + return Ok(Box::new(SupportsQuery::Property(ParenProperty { + span: self.i.make_span(start), + property, + }))); + } + + expect!(self, "("); // This fails + + unreachable!() + } + + fn parse_keyframes_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_keyframes_rule); + + let name = self.parse_word()?; + + expect!(self, "{"); + + let mut keyframes = vec![]; + + while let Some(..) = self.i.cur() { + if is!(self, "}") { + break; + } + let el = self.parse_keyframe_element()?; + + keyframes.push(el); + } + + expect!(self, "}"); + + Ok(KeyframesRule { + span: self.i.make_span(start), + name, + keyframes, + }) + } + + fn parse_keyframe_element(&mut self) -> PResult { + trace_cur!(self, parse_keyframe_element); + + let start = self.i.cur_pos(); + + let selector = self.parse_keyframe_selector()?; + let block = self.parse_decl_block()?; + + Ok(KeyframeElement { + span: self.i.make_span(start), + selector, + block, + }) + } + + fn parse_keyframe_selector(&mut self) -> PResult { + trace_cur!(self, parse_keyframe_selector); + + let word = self.parse_opt_word()?; + + Ok(match word { + Some(v) => KeyframeSelector::Extra(v), + None => { + let percent = self.parse_percent_selector()?; + + KeyframeSelector::Percent(percent) + } + }) + } + + fn parse_percent_selector(&mut self) -> PResult { + trace_cur!(self, parse_percent_selector); + + unimplemented!("parse_percent_selector") + } + + fn parse_font_face_rule(&mut self, start: BytePos) -> PResult { + trace_cur!(self, parse_font_face_rule); + + let block = self.parse_decl_block()?; + + Ok(FontFaceRule { + span: self.i.make_span(start), + block, + }) + } + + fn parse_unknown_at_rule(&mut self, _start: BytePos) -> PResult { + trace_cur!(self, parse_unknown_at_rule); + + unimplemented!("parse_unknown_at_rule") + } +} diff --git a/css/parser/src/parser/mod.rs b/css/parser/src/parser/mod.rs new file mode 100644 index 000000000000..a2984c1dd17e --- /dev/null +++ b/css/parser/src/parser/mod.rs @@ -0,0 +1,50 @@ +use crate::{error::Error, lexer::Lexer, token::Token}; +use swc_common::Span; +use swc_css_ast::*; + +mod at_rule; +mod selector; +mod style_rule; +mod util; +mod value; + +pub type PResult = Result; + +pub struct Parser<'i> { + i: Lexer<'i>, +} + +impl<'i> Parser<'i> { + pub(crate) fn new(i: Lexer<'i>) -> Self { + Self { i } + } +} + +impl Parser<'_> { + pub fn parse(&mut self) -> PResult { + let start = self.i.cur_pos(); + let mut rules = vec![]; + + while let Some(..) = self.i.cur() { + let rule = self.parse_rule()?; + + rules.push(rule); + } + + Ok(Stylesheet { + span: Span::new(start, self.i.prev_hi(), Default::default()), + rules, + }) + } + + fn parse_rule(&mut self) -> PResult { + trace_cur!(self, parse_rule); + + assert_ne!(self.i.cur(), None); + + match self.i.cur().unwrap() { + Token::At => self.parse_at_rule().map(Rule::from), + _ => self.parse_style_rule().map(From::from), + } + } +} diff --git a/css/parser/src/parser/selector.rs b/css/parser/src/parser/selector.rs new file mode 100644 index 000000000000..1a7e55eb5941 --- /dev/null +++ b/css/parser/src/parser/selector.rs @@ -0,0 +1,254 @@ +use crate::{PResult, Parser}; +use swc_css_ast::*; + +impl Parser<'_> { + pub(super) fn parse_selectors(&mut self) -> PResult>> { + let mut selectors = vec![]; + + loop { + let selector = self.parse_selector()?; + selectors.push(selector); + + if is_one_of!(self, ".", "#", "[", "*") || self.is_word() { + continue; + } + + if !eat!(self, ",") { + break; + } + } + + Ok(selectors) + } + + pub(super) fn parse_selector(&mut self) -> PResult> { + trace_cur!(self, parse_selector); + + let start = self.i.cur_pos(); + let mut components = vec![]; + + loop { + let sel = self + .parse_compound_selector() + .map(SelectorComponent::Compound)?; + + components.push(sel); + + if !is_one_of!(self, ">", "+", "~") { + break; + } + + components.push( + self.parse_selector_combinator() + .map(SelectorComponent::Combinator)?, + ); + continue; + } + + Ok(Box::new(Selector { + span: self.i.make_span(start), + components, + })) + } + + fn parse_compound_selector(&mut self) -> PResult { + trace_cur!(self, parse_compound_selector); + + let start = self.i.cur_pos(); + let mut selectors = vec![]; + + loop { + let selector = self.parse_simple_selector()?; + + selectors.push(selector); + + if !is_one_of!(self, ".", "#", "[", "*", ":") && !self.is_word() { + break; + } + } + + Ok(CompoundSelector { + span: self.i.make_span(start), + selectors, + }) + } + + fn parse_selector_combinator(&mut self) -> PResult { + let span = self.i.span(); + let combinator = if eat!(self, ">") { + Combinator::Child + } else if eat!(self, "+") { + Combinator::NextSibling + } else if eat!(self, "~") { + Combinator::FollowingSibling + } else { + expected_one_of!(self, ">", "+", "~") + }; + + Ok(CombinatorSelector { span, combinator }) + } + + fn parse_simple_selector(&mut self) -> PResult { + trace_cur!(self, parse_simple_selector); + + match cur!(self) { + tok!("*") => self + .parse_universal_selector() + .map(SimpleSelector::Universal), + tok!(":") => self.parse_pseudo_selector().map(SimpleSelector::Pseudo), + + tok!("#") => self.parse_id_selector().map(SimpleSelector::Id), + tok!(".") => self.parse_class_selector().map(SimpleSelector::Class), + tok!("[") => self + .parse_attribute_selector() + .map(SimpleSelector::Attribute), + _ => self.parse_tag_selector().map(SimpleSelector::Tag), + } + } + + fn parse_universal_selector(&mut self) -> PResult { + trace_cur!(self, parse_universal_selector); + debug_assert_eq!(self.i.cur(), Some(tok!("*"))); + let span = self.i.span(); + self.i.bump(); // '*' + + Ok(UniversalSelector { span }) + } + + fn parse_id_selector(&mut self) -> PResult { + trace_cur!(self, parse_id_selector); + debug_assert_eq!(self.i.cur(), Some(tok!("#"))); + let start = self.i.cur_pos(); + self.i.bump(); // '#' + + let text = self.parse_word()?; + + Ok(IdSelector { + span: self.i.make_span(start), + text, + }) + } + + fn parse_class_selector(&mut self) -> PResult { + trace_cur!(self, parse_class_selector); + + let start = self.i.cur_pos(); + debug_assert_eq!(self.i.cur(), Some(tok!("."))); + self.i.bump(); // '.' + + let text = self.parse_word()?; + + Ok(ClassSelector { + span: self.i.make_span(start), + text, + }) + } + + fn parse_tag_selector(&mut self) -> PResult { + trace_cur!(self, parse_tag_selector); + + let start = self.i.cur_pos(); + let text = self.parse_word()?; + + Ok(TagSelector { + span: self.i.make_span(start), + text, + }) + } + + fn parse_attribute_selector(&mut self) -> PResult { + trace_cur!(self, parse_attribute_selector); + debug_assert_eq!(self.i.cur(), Some(tok!("["))); + let start = self.i.cur_pos(); + self.i.bump(); // '[' + + let name = self.parse_word()?; + + let op = self.parse_attribute_selector_op()?; + let value = if op == AttributeOp::Any { + None + } else { + self.parse_word().map(Some)? + }; + expect!(self, "]"); + + Ok(AttributeSelector { + span: self.i.make_span(start), + attr: name, + value, + // TODO: Fix this + modifier: None, + op, + }) + } + + fn parse_attribute_selector_op(&mut self) -> PResult { + let op = match cur!(self) { + tok!("]") => return Ok(AttributeOp::Any), + tok!("=") => AttributeOp::Equals, + tok!("~=") => AttributeOp::Include, + tok!("|=") => AttributeOp::Dash, + tok!("^=") => AttributeOp::Prefix, + tok!("$=") => AttributeOp::Suffix, + tok!("*=") => AttributeOp::Contains, + _ => expected_one_of!(self, "]", "=", "~=", "|=", "^=", "$=", "*="), + }; + + self.i.bump(); + Ok(op) + } + + fn parse_pseudo_selector(&mut self) -> PResult { + debug_assert_eq!(self.i.cur(), Some(tok!(":"))); + let start = self.i.cur_pos(); + self.i.bump(); // ':' + + // TODO: Check for difference + let _ = eat!(self, ":"); + + let name = self.parse_word()?; + + let arguments = if is!(self, "(") { + Some(self.parse_pseudo_selector_args()?) + } else { + None + }; + + Ok(PseudoSelector { + span: self.i.make_span(start), + name, + // TODO + is_class: false, + // TODO + is_syntactic_class: false, + arguments, + // TODO + selector: None, + }) + } + + fn parse_pseudo_selector_args(&mut self) -> PResult> { + debug_assert_eq!(self.i.cur(), Some(tok!("("))); + self.i.bump(); // '(' + + let mut args = vec![]; + + loop { + if eat!(self, ")") { + break; + } + if self.is_word() { + args.push(self.parse_word()?); + if is!(self, ")") { + continue; + } + expect!(self, ","); + continue; + } + + unimplemented!("parse_pseudo_selector_args") + } + + Ok(args) + } +} diff --git a/css/parser/src/parser/style_rule.rs b/css/parser/src/parser/style_rule.rs new file mode 100644 index 000000000000..e70091578b81 --- /dev/null +++ b/css/parser/src/parser/style_rule.rs @@ -0,0 +1,71 @@ +use crate::{PResult, Parser}; +use swc_css_ast::*; + +impl Parser<'_> { + pub(super) fn parse_style_rule(&mut self) -> PResult { + trace_cur!(self, parse_style_rule); + + let start = self.i.cur_pos(); + let selectors = self.parse_selectors()?; + + let block = self.parse_decl_block()?; + + Ok(StyleRule { + span: self.i.make_span(start), + selectors, + block, + }) + } + + pub(super) fn parse_decl_block(&mut self) -> PResult { + trace_cur!(self, parse_decl_block); + + let start = self.i.cur_pos(); + expect!(self, "{"); + let mut properties = vec![]; + + while let Some(token) = self.i.cur() { + if token == tok!("}") { + break; + } + + let p = self.parse_property()?; + properties.push(p); + if !eat!(self, ";") && !is!(self, "}") { + break; + } + } + + expect!(self, "}"); + + Ok(DeclBlock { + span: self.i.make_span(start), + properties, + }) + } + + pub(super) fn parse_property(&mut self) -> PResult { + trace_cur!(self, parse_property); + + let start = self.i.cur_pos(); + + let name = self.parse_word()?; + expect!(self, ":"); + let value = self.parse_value()?; + + let important = if is!(self, "!important") { + let span = self.i.span(); + self.i.bump(); + Some(span) + } else { + None + }; + + Ok(Property { + span: self.i.make_span(start), + name, + value, + important, + }) + } +} diff --git a/css/parser/src/parser/util.rs b/css/parser/src/parser/util.rs new file mode 100644 index 000000000000..e1ecd8df49b5 --- /dev/null +++ b/css/parser/src/parser/util.rs @@ -0,0 +1,120 @@ +use crate::{token::Token, Error, PResult, Parser, SyntaxError}; +use swc_atoms::JsWord; +use swc_common::Span; +use swc_css_ast::*; + +impl<'i> Parser<'i> { + pub(super) fn parse_str(&mut self) -> PResult { + trace_cur!(self, parse_str); + + match self.i.cur() { + Some(t) => match t { + Token::Str => { + let span = self.i.span(); + let s = self.i.slice(); + self.i.bump(); + + Ok(Str { + span, + sym: s.into(), + }) + } + _ => self.err(SyntaxError::ExpectedStr { + got: format!("{:?}", t), + }), + }, + None => self.err(SyntaxError::Eof), + } + } + + pub(super) fn parse_word(&mut self) -> PResult { + trace_cur!(self, parse_word); + + let token = self.i.cur(); + let word = self.parse_opt_word()?; + + match word { + Some(v) => Ok(v), + None => self.err(SyntaxError::ExpectedWord { + got: format!("{:?}", token), + }), + } + } + + /// Eat one word, but not punctuntions or spaces. + pub(super) fn parse_opt_word(&mut self) -> PResult> { + trace_cur!(self, parse_opt_word); + + match self.i.cur() { + Some(t) => { + if let Some(sym) = as_word(t, self.i.slice()) { + let span = self.i.span(); + self.i.bump(); + + return Ok(Some(Text { span, sym })); + } else { + Ok(None) + } + } + None => self.err(SyntaxError::Eof), + } + } + + #[cold] + #[inline(never)] + pub(super) fn err(&mut self, err: SyntaxError) -> PResult { + let span = self.i.span(); + self.err_span(span, err) + } + + #[cold] + #[inline(never)] + pub(super) fn err_span(&mut self, span: Span, err: SyntaxError) -> PResult { + Err(Error { + inner: Box::new((span, err)), + }) + } + + pub fn is_word(&mut self) -> bool { + match self.i.cur() { + Some(t) => as_word(t, self.i.slice()).is_some(), + None => false, + } + } +} + +// TODO: Optimize using js_word +fn as_word(t: Token, s: &str) -> Option { + match t { + Token::Tilde + | Token::Mul + | Token::LBracket + | Token::RBracket + | Token::Lt + | Token::Gt + | Token::Error + | Token::Semi + | Token::Colon + | Token::Comma + | Token::At + | Token::Str + | Token::LParen + | Token::RParen + | Token::LBrace + | Token::RBrace + | Token::Hash + | Token::Plus + | Token::Minus + | Token::Dot + | Token::Eq + | Token::DollarEq + | Token::MulEq + | Token::XorEq + | Token::OrEq + | Token::TildeEq + | Token::BangImportant => None, + + // TODO: Optimize using js_word + Token::Ident | Token::Px => Some(s.into()), + } +} diff --git a/css/parser/src/parser/value.rs b/css/parser/src/parser/value.rs new file mode 100644 index 000000000000..8acf8c524ff4 --- /dev/null +++ b/css/parser/src/parser/value.rs @@ -0,0 +1,98 @@ +use crate::{token::Token, PResult, Parser, SyntaxError}; +use swc_css_ast::*; + +impl Parser<'_> { + pub(super) fn parse_value(&mut self) -> PResult { + trace_cur!(self, parse_value); + + let word = self.parse_opt_word()?; + + match word { + Some(v) => return Ok(Value::Text(v)), + None => {} + } + + match cur!(self) { + Token::Str => self.parse_str().map(Value::Str), + + Token::Error + | Token::Semi + | Token::Colon + | Token::Comma + | Token::At + | Token::Dot + | Token::RParen + | Token::RBrace + | Token::LBrace + | Token::Mul + | Token::LBracket + | Token::RBracket + | Token::Lt + | Token::Eq + | Token::DollarEq + | Token::MulEq + | Token::XorEq + | Token::OrEq + | Token::TildeEq + | Token::Gt + | Token::Tilde + | Token::BangImportant => { + let got = format!("{:?}", self.i.cur().unwrap()); + self.err(SyntaxError::ExpectedOneOf { + expected: "value".into(), + got, + }) + } + + Token::LParen => self.parse_paren_value().map(Value::Paren), + Token::Minus | Token::Plus => self.parse_numeric_valie(), + Token::Hash => self.parse_hash_value(), + + Token::Ident | Token::Px => unreachable!(), + } + } + + fn parse_paren_value(&mut self) -> PResult { + trace_cur!(self, parse_paren_value); + + debug_assert_eq!(self.i.cur(), Some(Token::LParen)); + let start = self.i.cur_pos(); + + self.i.bump(); // '(' + + let value = self.parse_value().map(Box::new)?; + + expect!(self, ")"); + + Ok(ParenValue { + span: self.i.make_span(start), + value, + }) + } + + fn parse_numeric_valie(&mut self) -> PResult { + trace_cur!(self, parse_numeric_valie); + + let _is_plus = match self.i.cur() { + Some(Token::Plus) => { + self.i.bump(); // '+' + + true + } + Some(Token::Minus) => { + self.i.bump(); // '-' + + false + } + _ => unreachable!(), + }; + + unimplemented!("parse_numeric_valie") + } + + fn parse_hash_value(&mut self) -> PResult { + trace_cur!(self, parse_hash_value); + + unimplemented!("parse_hash_value") + } +} diff --git a/css/parser/src/token.rs b/css/parser/src/token.rs new file mode 100644 index 000000000000..51ce132d60f7 --- /dev/null +++ b/css/parser/src/token.rs @@ -0,0 +1,104 @@ +use logos::{Logos, Skip}; + +#[derive(Debug, Default, Clone, Copy)] +pub struct Extras { + pub had_whitespace: bool, +} + +/// This does not implements [PartialEq] nor [Eq] as it's not optimized out. +#[derive(Logos, Debug, Clone, Copy, PartialEq, Eq)] +#[logos(extras = Extras)] +pub(crate) enum Token { + #[error] + #[regex("[ \n\t\r]*", lex_ws)] + Error, + + #[token(";")] + Semi, + + #[token(":")] + Colon, + + #[token(",")] + Comma, + + #[token("@")] + At, + + #[token("*")] + Mul, + + /// Quoted string + #[regex(r#"'(?:[^']|\\[.\\])*'"#)] + #[regex("\"(?:[^\"]|\\\\[.\\\\])*\"")] + Str, + + #[regex(r#"[a-zA-Z\-][a-zA-Z\-]*"#)] + Ident, + + #[token("(")] + LParen, + + #[token(")")] + RParen, + + #[token("{")] + LBrace, + #[token("}")] + RBrace, + + #[token("[")] + LBracket, + #[token("]")] + RBracket, + + #[token("+")] + Plus, + + #[token("-")] + Minus, + + #[token(".")] + Dot, + + #[token("#")] + Hash, + + #[token("important")] + BangImportant, + + #[token("px")] + Px, + + #[token("<")] + Lt, + #[token(">")] + Gt, + + #[token("=")] + Eq, + + #[token("$=")] + DollarEq, + + #[token("*=")] + MulEq, + + #[token("^=")] + XorEq, + + #[token("|=")] + OrEq, + + #[token("~=")] + TildeEq, + + #[token("~")] + Tilde, +} + +fn lex_ws(lex: &mut logos::Lexer) -> Skip { + lex.extras.had_whitespace = true; + + Skip +} diff --git a/css/parser/tests/fixture.rs b/css/parser/tests/fixture.rs new file mode 100644 index 000000000000..16436b5911a6 --- /dev/null +++ b/css/parser/tests/fixture.rs @@ -0,0 +1,74 @@ +#![feature(test)] + +extern crate test; + +use std::{env, path::PathBuf}; +use swc_css_parser::parse; +use test::{ + test_main, DynTestFn, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestName, TestType, +}; +use testing::NormalizedOutput; +use walkdir::WalkDir; + +fn add_test( + tests: &mut Vec, + name: String, + ignore: bool, + f: F, +) { + tests.push(TestDescAndFn { + desc: TestDesc { + test_type: TestType::UnitTest, + name: TestName::DynTestName(name), + ignore, + should_panic: No, + allow_fail: false, + }, + testfn: DynTestFn(Box::new(f)), + }); +} + +fn load_fixtures(tests: &mut Vec) { + let root = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not specified")) + .join("tests") + .join("fixture"); + + for entry in WalkDir::new(&root) { + let entry = entry.unwrap(); + if entry.file_type().is_dir() || !entry.file_name().to_string_lossy().ends_with(".css") { + continue; + } + + add_test( + tests, + entry.file_name().to_string_lossy().replace("/", "::"), + false, + || { + testing::run_test2(false, move |cm, _handler| { + let fm = cm.load_file(entry.path()).unwrap(); + + let stylesheet = parse(fm.start_pos, &fm.src).unwrap(); + let json = serde_json::to_string_pretty(&stylesheet).unwrap(); + + let output = NormalizedOutput::from(json); + + output + .compare_to_file(entry.path().with_extension("json")) + .unwrap(); + + Ok(()) + }) + .unwrap() + }, + ); + } +} + +#[test] +fn fixture() { + let args: Vec<_> = env::args().collect(); + let mut tests = Vec::new(); + load_fixtures(&mut tests); + test_main(&args, tests, Some(Options::new())); +} diff --git a/css/parser/tests/fixture/selector/basic.css b/css/parser/tests/fixture/selector/basic.css new file mode 100644 index 000000000000..da5bc06d6f81 --- /dev/null +++ b/css/parser/tests/fixture/selector/basic.css @@ -0,0 +1,8 @@ +a { +} + +.b { +} + +#c { +} diff --git a/css/parser/tests/fixture/selector/basic.json b/css/parser/tests/fixture/selector/basic.json new file mode 100644 index 000000000000..81f4c38ade85 --- /dev/null +++ b/css/parser/tests/fixture/selector/basic.json @@ -0,0 +1,159 @@ +{ + "span": { + "start": 0, + "end": 21, + "ctxt": 0 + }, + "rules": [ + { + "span": { + "start": 0, + "end": 4, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "text": { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "sym": "a" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 2, + "end": 4, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 7, + "end": 12, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 7, + "end": 9, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 7, + "end": 9, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 7, + "end": 9, + "ctxt": 0 + }, + "text": { + "span": { + "start": 8, + "end": 9, + "ctxt": 0 + }, + "sym": "b" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 10, + "end": 12, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 15, + "end": 20, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 15, + "end": 17, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 15, + "end": 17, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 15, + "end": 17, + "ctxt": 0 + }, + "text": { + "span": { + "start": 16, + "end": 17, + "ctxt": 0 + }, + "sym": "c" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 18, + "end": 20, + "ctxt": 0 + }, + "properties": [] + } + } + ] +} diff --git a/css/parser/tests/fixture/selector/intersection.css b/css/parser/tests/fixture/selector/intersection.css new file mode 100644 index 000000000000..c984d54c586b --- /dev/null +++ b/css/parser/tests/fixture/selector/intersection.css @@ -0,0 +1,8 @@ +a.my-class { +} + +.b.second.thrid { +} + +#c.is-this-used { +} diff --git a/css/parser/tests/fixture/selector/intersection.json b/css/parser/tests/fixture/selector/intersection.json new file mode 100644 index 000000000000..ce9d29b98c6b --- /dev/null +++ b/css/parser/tests/fixture/selector/intersection.json @@ -0,0 +1,219 @@ +{ + "span": { + "start": 0, + "end": 56, + "ctxt": 0 + }, + "rules": [ + { + "span": { + "start": 0, + "end": 13, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 10, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 0, + "end": 10, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "text": { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "sym": "a" + } + }, + { + "span": { + "start": 1, + "end": 10, + "ctxt": 0 + }, + "text": { + "span": { + "start": 2, + "end": 10, + "ctxt": 0 + }, + "sym": "my-class" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 11, + "end": 13, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 16, + "end": 34, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 16, + "end": 31, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 16, + "end": 31, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 16, + "end": 18, + "ctxt": 0 + }, + "text": { + "span": { + "start": 17, + "end": 18, + "ctxt": 0 + }, + "sym": "b" + } + }, + { + "span": { + "start": 18, + "end": 25, + "ctxt": 0 + }, + "text": { + "span": { + "start": 19, + "end": 25, + "ctxt": 0 + }, + "sym": "second" + } + }, + { + "span": { + "start": 25, + "end": 31, + "ctxt": 0 + }, + "text": { + "span": { + "start": 26, + "end": 31, + "ctxt": 0 + }, + "sym": "thrid" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 32, + "end": 34, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 37, + "end": 55, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 37, + "end": 52, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 37, + "end": 52, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 37, + "end": 39, + "ctxt": 0 + }, + "text": { + "span": { + "start": 38, + "end": 39, + "ctxt": 0 + }, + "sym": "c" + } + }, + { + "span": { + "start": 39, + "end": 52, + "ctxt": 0 + }, + "text": { + "span": { + "start": 40, + "end": 52, + "ctxt": 0 + }, + "sym": "is-this-used" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 53, + "end": 55, + "ctxt": 0 + }, + "properties": [] + } + } + ] +} diff --git a/css/parser/tests/fixture/selector/union.css b/css/parser/tests/fixture/selector/union.css new file mode 100644 index 000000000000..7da3ee852acb --- /dev/null +++ b/css/parser/tests/fixture/selector/union.css @@ -0,0 +1,15 @@ +a, +p { +} + +.b, +.c { +} + +#d, +#e { +} + +.d, +#e { +} diff --git a/css/parser/tests/fixture/selector/union.json b/css/parser/tests/fixture/selector/union.json new file mode 100644 index 000000000000..7aae1289e59e --- /dev/null +++ b/css/parser/tests/fixture/selector/union.json @@ -0,0 +1,341 @@ +{ + "span": { + "start": 0, + "end": 44, + "ctxt": 0 + }, + "rules": [ + { + "span": { + "start": 0, + "end": 7, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "text": { + "span": { + "start": 0, + "end": 1, + "ctxt": 0 + }, + "sym": "a" + } + } + ] + } + ] + }, + { + "span": { + "start": 3, + "end": 4, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 3, + "end": 4, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 3, + "end": 3, + "ctxt": 0 + }, + "text": { + "span": { + "start": 3, + "end": 4, + "ctxt": 0 + }, + "sym": "p" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 5, + "end": 7, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 10, + "end": 19, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 10, + "end": 12, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 10, + "end": 12, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 10, + "end": 12, + "ctxt": 0 + }, + "text": { + "span": { + "start": 11, + "end": 12, + "ctxt": 0 + }, + "sym": "b" + } + } + ] + } + ] + }, + { + "span": { + "start": 14, + "end": 16, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 14, + "end": 16, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 14, + "end": 16, + "ctxt": 0 + }, + "text": { + "span": { + "start": 15, + "end": 16, + "ctxt": 0 + }, + "sym": "c" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 17, + "end": 19, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 22, + "end": 31, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 22, + "end": 24, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 22, + "end": 24, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 22, + "end": 24, + "ctxt": 0 + }, + "text": { + "span": { + "start": 23, + "end": 24, + "ctxt": 0 + }, + "sym": "d" + } + } + ] + } + ] + }, + { + "span": { + "start": 26, + "end": 28, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 26, + "end": 28, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 26, + "end": 28, + "ctxt": 0 + }, + "text": { + "span": { + "start": 27, + "end": 28, + "ctxt": 0 + }, + "sym": "e" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 29, + "end": 31, + "ctxt": 0 + }, + "properties": [] + } + }, + { + "span": { + "start": 34, + "end": 43, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 34, + "end": 36, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 34, + "end": 36, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 34, + "end": 36, + "ctxt": 0 + }, + "text": { + "span": { + "start": 35, + "end": 36, + "ctxt": 0 + }, + "sym": "d" + } + } + ] + } + ] + }, + { + "span": { + "start": 38, + "end": 40, + "ctxt": 0 + }, + "components": [ + { + "span": { + "start": 38, + "end": 40, + "ctxt": 0 + }, + "selectors": [ + { + "span": { + "start": 38, + "end": 40, + "ctxt": 0 + }, + "text": { + "span": { + "start": 39, + "end": 40, + "ctxt": 0 + }, + "sym": "e" + } + } + ] + } + ] + } + ], + "block": { + "span": { + "start": 41, + "end": 43, + "ctxt": 0 + }, + "properties": [] + } + } + ] +} diff --git a/css/parser/tests/grass_addition.rs b/css/parser/tests/grass_addition.rs new file mode 100644 index 000000000000..30a370c063f7 --- /dev/null +++ b/css/parser/tests/grass_addition.rs @@ -0,0 +1,385 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + undefined_function_call_is_ident_adds, + "a {\n color: 1 + foo();\n}\n", + "a {\n color: 1foo();\n}\n" +); +grass_test!( + unitless_plus_null, + "a {\n color: 1 + null;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + unitless_plus_null_plus_unitless, + "a {\n color: 1 + null + 1;\n}\n", + "a {\n color: 11;\n}\n" +); +grass_test!( + unit_plus_null, + "a {\n color: 1px + null;\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + chain_ident_addition, + "a {\n color: a + b + c + d + e + f;\n}\n", + "a {\n color: abcdef;\n}\n" +); +grass_test!( + unquoted_plus_unquoted, + "a {\n color: foo + bar;\n}\n", + "a {\n color: foobar;\n}\n" +); +grass_test!( + dblquoted_plus_dblquoted, + "a {\n color: \"foo\" + \"bar\";\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + sglquoted_plus_sglquoted, + "a {\n color: 'foo' + 'bar';\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + dblquoted_plus_unquoted, + "a {\n color: \"foo\" + bar;\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + sglquoted_plus_unquoted, + "a {\n color: 'foo' + bar;\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + unquoted_plus_dblquoted, + "a {\n color: foo + \"bar\";\n}\n", + "a {\n color: foobar;\n}\n" +); +grass_test!( + unquoted_plus_sglquoted, + "a {\n color: foo + 'bar';\n}\n", + "a {\n color: foobar;\n}\n" +); +grass_test!( + sglquoted_plus_dblquoted, + "a {\n color: 'foo' + \"bar\";\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + dblquoted_plus_sglquoted, + "a {\n color: \"foo\" + 'bar';\n}\n", + "a {\n color: \"foobar\";\n}\n" +); +grass_test!( + unquoted_plus_true, + "a {\n color: foo + true;\n}\n", + "a {\n color: footrue;\n}\n" +); +grass_test!( + dblquoted_plus_true, + "a {\n color: \"foo\" + true;\n}\n", + "a {\n color: \"footrue\";\n}\n" +); +grass_test!( + sglquoted_plus_true, + "a {\n color: 'foo' + true;\n}\n", + "a {\n color: \"footrue\";\n}\n" +); +grass_test!( + unquoted_plus_false, + "a {\n color: foo + false;\n}\n", + "a {\n color: foofalse;\n}\n" +); +grass_test!( + dblquoted_plus_false, + "a {\n color: \"foo\" + false;\n}\n", + "a {\n color: \"foofalse\";\n}\n" +); +grass_test!( + sglquoted_plus_false, + "a {\n color: 'foo' + false;\n}\n", + "a {\n color: \"foofalse\";\n}\n" +); +grass_test!( + unquoted_plus_important, + "a {\n color: foo + !important;\n}\n", + "a {\n color: foo!important;\n}\n" +); +grass_test!( + unquoted_plus_important_uppercase, + "a {\n color: foo + !IMPORTANT;\n}\n", + "a {\n color: foo!important;\n}\n" +); +grass_test!( + unquoted_plus_null, + "a {\n color: foo + null;\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + dblquoted_plus_null, + "a {\n color: \"foo\" + null;\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + sglquoted_plus_null, + "a {\n color: 'foo' + null;\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + unquoted_plus_number_unitless, + "a {\n color: foo + 1;\n}\n", + "a {\n color: foo1;\n}\n" +); +grass_test!( + dblquoted_plus_number_unitless, + "a {\n color: \"foo\" + 1;\n}\n", + "a {\n color: \"foo1\";\n}\n" +); +grass_test!( + sglquoted_plus_number_unitless, + "a {\n color: 'foo' + 1;\n}\n", + "a {\n color: \"foo1\";\n}\n" +); +grass_test!( + unquoted_plus_number_unit, + "a {\n color: foo + 1px;\n}\n", + "a {\n color: foo1px;\n}\n" +); +grass_test!( + dblquoted_plus_number_unit, + "a {\n color: \"foo\" + 1px;\n}\n", + "a {\n color: \"foo1px\";\n}\n" +); +grass_test!( + sglquoted_plus_number_unit, + "a {\n color: 'foo' + 1px;\n}\n", + "a {\n color: \"foo1px\";\n}\n" +); +grass_test!( + true_plus_false, + "a {\n color: true + false;\n}\n", + "a {\n color: truefalse;\n}\n" +); +grass_test!( + true_plus_dblquoted, + "a {\n color: true + \"foo\";\n}\n", + "a {\n color: \"truefoo\";\n}\n" +); +grass_test!( + false_plus_dblquoted, + "a {\n color: false + \"foo\";\n}\n", + "a {\n color: \"falsefoo\";\n}\n" +); +grass_test!( + true_plus_unquoted, + "a {\n color: true + foo;\n}\n", + "a {\n color: truefoo;\n}\n" +); +grass_test!( + false_plus_unquoted, + "a {\n color: false + foo;\n}\n", + "a {\n color: falsefoo;\n}\n" +); +grass_test!( + true_plus_null, + "a {\n color: true + null;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + false_plus_null, + "a {\n color: false + null;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + true_plus_null_is_string, + "a {\n color: type-of(true + null);\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + false_plus_null_is_string, + "a {\n color: type-of(false + null);\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + null_plus_num, + "a {\n color: null + 1;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + null_plus_num_is_string, + "a {\n color: type-of(null + 1);\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + number_plus_list, + "a {\n color: 1 + (2 3);\n}\n", + "a {\n color: 12 3;\n}\n" +); +grass_test!( + list_plus_num, + "a {\n color: (1 2) + 3;\n}\n", + "a {\n color: 1 23;\n}\n" +); +grass_test!( + dblquoted_plus_list, + "a {\n color: \"1\" + (2 3);\n}\n", + "a {\n color: \"12 3\";\n}\n" +); +grass_test!( + list_plus_dblquoted, + "a {\n color: (1 2) + \"3\";\n}\n", + "a {\n color: \"1 23\";\n}\n" +); +grass_test!( + sglquoted_plus_list, + "a {\n color: 'a' + (b c);\n}\n", + "a {\n color: \"ab c\";\n}\n" +); +grass_test!( + list_plus_sglquoted, + "a {\n color: (b c) + 'a';\n}\n", + "a {\n color: \"b ca\";\n}\n" +); +grass_test!( + list_plus_list, + "a {\n color: (a b) + (1 2);\n}\n", + "a {\n color: a b1 2;\n}\n" +); +grass_test!( + multiple_ident_sum, + "a {\n color: foo + 1 + bar + 2;\n}\n", + "a {\n color: foo1bar2;\n}\n" +); +grass_test!( + dblquoted_plus_named_color, + "a {\n color: \"foo\" + red;\n}\n", + "a {\n color: \"foored\";\n}\n" +); +grass_test!( + sglquoted_plus_named_color, + "a {\n color: 'foo' + red;\n}\n", + "a {\n color: \"foored\";\n}\n" +); +grass_test!( + unquoted_plus_named_color, + "a {\n color: foo + red;\n}\n", + "a {\n color: foored;\n}\n" +); +grass_test!( + dblquoted_plus_hex_color, + "a {\n color: \"foo\" + #fff;\n}\n", + "a {\n color: \"foo#fff\";\n}\n" +); +grass_test!( + sglquoted_plus_hex_color, + "a {\n color: 'foo' + #fff;\n}\n", + "a {\n color: \"foo#fff\";\n}\n" +); +grass_test!( + unquoted_plus_hex_color, + "a {\n color: foo + #fff;\n}\n", + "a {\n color: foo#fff;\n}\n" +); +grass_test!( + number_plus_true, + "a {\n color: 1 + true;\n}\n", + "a {\n color: 1true;\n}\n" +); +grass_test!( + number_plus_false, + "a {\n color: 1 + false;\n}\n", + "a {\n color: 1false;\n}\n" +); +grass_test!( + number_plus_unary_not, + "a {\n color: 1 + not 2;\n}\n", + "a {\n color: 1false;\n}\n" +); +grass_test!( + number_plus_important, + "a {\n color: 1 + !important;\n}\n", + "a {\n color: 1!important;\n}\n" +); +grass_test!( + number_plus_arglist, + "@function foo($a...) { + @return 1+$a; + } + + a { + color: foo(a, b, c); + }", + "a {\n color: 1a, b, c;\n}\n" +); +grass_error!( + number_plus_named_color, + "a {\n color: 1 + red;\n}\n", + "Error: Undefined operation \"1 + red\"." +); +grass_test!( + double_plus, + "a {\n color: 1 ++ 2;\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + plus_div, + "a {\n color: 1+/2;\n}\n", + "a {\n color: 1/2;\n}\n" +); +grass_test!( + arglist_plus_number, + "@function foo($a...) { + @return $a + 1; + } + + a { + color: foo(a, b); + }", + "a {\n color: a, b1;\n}\n" +); +grass_test!( + color_plus_ident, + "a {\n color: red + foo;\n}\n", + "a {\n color: redfoo;\n}\n" +); +grass_test!( + ident_plus_color, + "a {\n color: foo + red;\n}\n", + "a {\n color: foored;\n}\n" +); +grass_test!( + important_plus_dblquoted, + "a {\n color: !important + \"foo\";\n}\n", + "a {\n color: !importantfoo;\n}\n" +); +grass_test!( + important_plus_null, + "a {\n color: !important + null;\n}\n", + "a {\n color: !important;\n}\n" +); +grass_test!( + important_plus_unquoted, + "a {\n color: !important + foo;\n}\n", + "a {\n color: !importantfoo;\n}\n" +); +grass_error!( + map_lhs_add, + "a {color: (a: b) + 1;}", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + map_rhs_add, + "a {color: 1 + (a: b);}", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + function_lhs_add, + "a {color: get-function(lighten) + 1;}", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_error!( + function_rhs_add, + "a {color: 1 + get-function(lighten);}", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); diff --git a/css/parser/tests/grass_and.rs b/css/parser/tests/grass_and.rs new file mode 100644 index 000000000000..1fd0167a6f19 --- /dev/null +++ b/css/parser/tests/grass_and.rs @@ -0,0 +1,61 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + one_and_two, + "a {\n color: 1 and 2;\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + two_and_one, + "a {\n color: 2 and 1;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + true_and_true, + "a {\n color: true and true;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + true_and_false, + "a {\n color: true and false;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + false_and_true, + "a {\n color: false and true;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + false_and_false, + "a {\n color: false and false;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!(null_and_one, "a {\n color: null and 1;\n}\n", ""); +grass_test!(one_and_null, "a {\n color: 1 and null;\n}\n", ""); +grass_test!( + one_and_two_and_three, + "a {\n color: 1 and 2 and 3;\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + part_of_binop, + "a {\n color: 1 - and;\n}\n", + "a {\n color: 1-and;\n}\n" +); +grass_test!( + part_of_binop_casing, + "a {\n color: 1 - AND;\n}\n", + "a {\n color: 1-AND;\n}\n" +); +grass_test!( + short_circuits_when_lhs_is_false, + "a {\n color: false and comparable(\"a\", \"b\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_error!( + #[ignore = "blocked on a rewrite of value eval"] + properly_bubbles_error_when_invalid_char_after_and, + "a {\n color: false and? foo;\n}\n", + "Error: expected \";\"." +); diff --git a/css/parser/tests/grass_arglist.rs b/css/parser/tests/grass_arglist.rs new file mode 100644 index 000000000000..239205b6b40c --- /dev/null +++ b/css/parser/tests/grass_arglist.rs @@ -0,0 +1,94 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + length_of_empty_arglist, + "@mixin foo($a...) {\n color: length($list: $a);\n}\na {\n @include foo;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + length_of_arglist_in_mixin, + "@mixin foo($a...) {\n color: length($list: $a);\n}\na {\n @include foo(a, 2, c);\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + arglist_in_at_each, + "@function sum($numbers...) { + $sum: 0; + + @each $number in $numbers { + $sum: $sum + $number; + } + @return $sum; + } + + a { + width: sum(50px, 30px, 100px); + }", + "a {\n width: 180px;\n}\n" +); +grass_error!( + emit_empty_arglist, + "@function foo($a...) { + @return $a; + } + + a { + color: foo(); + }", + "Error: () isn't a valid CSS value." +); +grass_test!( + inspect_empty_arglist, + "@function foo($a...) { + @return inspect($a); + } + + a { + color: foo(); + }", + "a {\n color: ();\n}\n" +); +grass_test!( + empty_arglist_is_allowed_in_map_functions, + "@function foo($a...) { + @return map-get($map: $a, $key: foo); + } + + a { + color: inspect(foo()); + }", + "a {\n color: null;\n}\n" +); +grass_test!( + inspect_arglist_with_one_arg, + "@function foo($a...) { + @return inspect($a); + } + + a { + color: inspect(foo(1)); + }", + "a {\n color: (1,);\n}\n" +); +grass_error!( + empty_arglist_is_error, + "@function foo($a...) { + @return $a; + } + + a { + color: foo(); + }", + "Error: () isn't a valid CSS value." +); +grass_test!( + arglist_of_only_null_is_null, + "@function foo($a...) { + @return $a; + } + a { + color: foo(null, null); + }", + "" +); diff --git a/css/parser/tests/grass_args.rs b/css/parser/tests/grass_args.rs new file mode 100644 index 000000000000..388eae75e218 --- /dev/null +++ b/css/parser/tests/grass_args.rs @@ -0,0 +1,228 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + variable_after_varargs, + "@function foo($a..., $b) {\n @return $a;\n}\n", + "Error: expected \")\"." +); +grass_error!( + varargs_one_period, + "@function foo($a.) {\n @return $a;\n}\n", + "Error: expected \".\"." +); +grass_error!( + varargs_two_periods, + "@function foo($a..) {\n @return $a;\n}\n", + "Error: expected \".\"." +); +grass_test!( + mixin_varargs_are_comma_separated, + "@mixin foo($a...) {\n color: $a;\n}\n\na {\n @include foo(1, 2, 3, 4, 5);\n}\n", + "a {\n color: 1, 2, 3, 4, 5;\n}\n" +); +grass_test!( + function_varargs_are_comma_separated, + "@function foo($a...) {\n @return $a;\n}\n\na {\n color: foo(1, 2, 3, 4, 5);\n}\n", + "a {\n color: 1, 2, 3, 4, 5;\n}\n" +); +grass_test!( + default_args_are_lazily_evaluated, + "$da: a;\n\n@mixin foo($x: $da) {\n color: $x;\n}\n$da: b;\n\na {\n @include foo();\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + variable_being_subtracted, + "$index: 1;\n\n@function foo($a) {\n @return $a;\n}\n\na {\n color: foo($index - 1);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + parens_in_default_arg_value, + "@function foo($arg1: bar()) {\n @return true;\n}\n\na {\n color: foo();\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + self_referential_default_arg_value, + "@function foo($a, $b: $a) {\n @return $b;\n}\n\na {\n color: foo(2);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + arg_errors_are_lazy_for_if, + "a {\n color: if(false, unit(foo), red);\n}\n", + "a {\n color: red;\n}\n" +); +grass_error!( + #[ignore = "expects incorrect char, '{'"] + nothing_after_open, + "a { color:rgb(; }", + "Error: expected \")\"." +); +grass_error!( + nothing_after_open_paren_in_fn_args, + "@function foo(", + "Error: expected \")\"." +); +grass_error!( + args_are_evaluated_eagerly, + "@function foo($a) {\n @return foo;\n}\n\na {\n color: foo(unit(bar));\n}\n", + "Error: $number: bar is not a number." +); +grass_test!( + variable_is_only_hyphens, + "$--: red; + + a { + color: foo($--); + }", + "a {\n color: foo(red);\n}\n" +); +grass_test!( + no_space_after_colon_in_keyword_arg, + "@function foo($a) { + @return $a; + } + + $b: red; + + a { + color: foo($a:$b); + }", + "a {\n color: red;\n}\n" +); +grass_test!( + comment_after_comma_in_func_args, + "@mixin a( + $foo,//foo + ) { + color: $foo; + } + + a { + @include a(red); + }", + "a {\n color: red;\n}\n" +); +grass_test!( + filter_one_arg, + "a {\n color: foo(a=a);\n}\n", + "a {\n color: foo(a=a);\n}\n" +); +grass_test!( + filter_two_args, + "a {\n color: foo(a=a, b=b);\n}\n", + "a {\n color: foo(a=a, b=b);\n}\n" +); +grass_test!( + filter_whitespace, + "a {\n color: foo( a = a );\n}\n", + "a {\n color: foo(a=a);\n}\n" +); +grass_test!( + filter_whitespace_list, + "a {\n color: foo( A a = a );\n}\n", + "a {\n color: foo(A a=a);\n}\n" +); +grass_test!( + filter_function_call, + "a {\n color: foo(hue(green)=hue(green));\n}\n", + "a {\n color: foo(120deg=120deg);\n}\n" +); +grass_test!( + filter_addition, + "a {\n color: foo(1+1=1+1);\n}\n", + "a {\n color: foo(2=2);\n}\n" +); +grass_test!( + filter_splat_of_single_value, + "a {\n color: foo(a=a...);\n}\n", + "a {\n color: foo(a=a);\n}\n" +); +grass_test!( + filter_splat_of_list, + "a {\n color: foo(a=[a, b]...);\n}\n", + "a {\n color: foo(a=[a, b]);\n}\n" +); +grass_test!( + filter_both_null, + "a {\n color: foo(null=null);\n}\n", + "a {\n color: foo(=);\n}\n" +); +grass_error!( + filter_splat_missing_third_period, + "a {\n color: foo(1 + 1 = a..);\n}\n", + "Error: expected \".\"." +); +grass_error!( + filter_invalid_css_value, + "a {\n color: foo((a: b)=a);\n}\n", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + filter_nothing_before_equal, + "a {\n color: foo(=a);\n}\n", + "Error: Expected expression." +); +grass_error!( + filter_nothing_after_equal, + "a {\n color: foo(a=);\n}\n", + "Error: Expected expression." +); +grass_error!( + filter_equal_is_last_char, + "a {\n color: foo(a=", + "Error: Expected expression." +); +grass_error!( + filter_value_after_equal_is_last_char, + "a {\n color: foo(a=a", + "Error: expected \")\"." +); +grass_error!( + unclosed_paren_in_nested_args, + "a { color: a(b(red); }", + "Error: expected \")\"." +); +grass_error!( + filter_rhs_missing_closing_paren, + "a { color: lighten(red=(green); }", + "Error: expected \")\"." +); +grass_test!( + space_after_loud_comment, + "@mixin foo($x) { + color: $x; + } + + a { + @include foo($x /* blah */ : kwd-y); + }", + "a {\n color: kwd-y;\n}\n" +); +grass_test!( + quoted_string_as_default_argument_value, + r#"@function foo($font-family: 'Roboto, "Helvetica Neue", sans-serif') { + @return $font-family; + } + + a { + color: foo(); + }"#, + "a {\n color: 'Roboto, \"Helvetica Neue\", sans-serif';\n}\n" +); +grass_test!( + args_do_not_affect_existing_outer_variables, + "@mixin mixin2($a) { + color: $a; + } + + @mixin mixin1($a) { + color: $a; + @include mixin2(bar); + color: $a; + } + + a { + @include mixin1(foo); + }", + "a {\n color: foo;\n color: bar;\n color: foo;\n}\n" +); diff --git a/css/parser/tests/grass_at_error.rs b/css/parser/tests/grass_at_error.rs new file mode 100644 index 000000000000..3d881a95ec3b --- /dev/null +++ b/css/parser/tests/grass_at_error.rs @@ -0,0 +1,20 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + error_dblquoted_string, + "a {\n @error \"hi\";\n}\n", + "Error: \"hi\"" +); +grass_error!( + error_sglquoted_string, + "a {\n @error 'hi';\n}\n", + "Error: \"hi\"" +); +grass_error!(error_unquoted_string, "a {\n @error hi;\n}\n", "Error: hi"); +grass_error!(error_arithmetic, "a {\n @error 1 + 1;\n}\n", "Error: 2"); +grass_error!( + error_is_inspected, + "a {\n @error null;\n}\n", + "Error: null" +); diff --git a/css/parser/tests/grass_at_root.rs b/css/parser/tests/grass_at_root.rs new file mode 100644 index 000000000000..6dd33538503c --- /dev/null +++ b/css/parser/tests/grass_at_root.rs @@ -0,0 +1,77 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + simple_nested, + ".foo {\n @at-root {\n .bar {a: b}\n }\n}\n", + ".bar {\n a: b;\n}\n" +); +grass_test!( + with_selector, + ".foo {\n @at-root .bar {a: b}\n}\n", + ".bar {\n a: b;\n}\n" +); +grass_test!( + with_selector_in_mixin, + "@mixin bar {\n @at-root .bar {a: b}\n}\n\n.foo {\n @include bar;\n}\n", + ".bar {\n a: b;\n}\n" +); +grass_test!( + with_super_selector, + ".foo {\n @at-root & {\n a: b;\n }\n}\n", + ".foo {\n a: b;\n}\n" +); +grass_test!( + nested_with_super_selector, + ".foo {\n @at-root & {\n .bar {\n @at-root & {\n a: b;\n }\n }\n \ + }\n}\n", + ".foo .bar {\n a: b;\n}\n" +); +grass_test!( + deeply_nested_with_rulesets_and_styles, + ".foo {\n @at-root .bar {\n a: b;\n c {\n d: e;\n foo {\n bar: baz;\n }\n h: j;\n }\n f: g;\n }\n}\n", + ".bar {\n a: b;\n f: g;\n}\n.bar c {\n d: e;\n h: j;\n}\n.bar c foo {\n bar: baz;\n}\n" +); +grass_test!( + super_selector_inside_with_nothing, + "foo {\n @at-root {\n & {\n color: bar;\n }\n }\n}\n", + "foo {\n color: bar;\n}\n" +); +grass_test!( + interpolated_super_selector_with_nothing, + "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n \ + }\n}\n", + "testpost foo {\n bar: baz;\n}\n" +); +grass_test!( + with_ampersand_single, + "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n \ + }\n}\n", + "testpost foo {\n bar: baz;\n}\n" +); +grass_test!( + root_interpolated_ampersand, + "@at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n}\n", + "post foo {\n bar: baz;\n}\n" +); +grass_test!( + nested_prefix_interpolated_ampersand, + "test {\n @at-root {\n pre#{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "pretest foo {\n bar: baz;\n}\n" +); +grass_test!( + nested_alone_interpolated_ampersand, + "test {\n @at-root {\n #{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "test foo {\n bar: baz;\n}\n" +); +grass_test!( + style_before_at_root, + "a {}\n\n@at-root {\n @-ms-viewport { width: device-width; }\n}\n", + "@-ms-viewport {\n width: device-width;\n}\n" +); +grass_error!( + #[ignore = "we do not currently validate missing closing curly braces"] + missing_closing_curly_brace, + "@at-root {", + "Error: expected \"}\"." +); diff --git a/css/parser/tests/grass_charset.rs b/css/parser/tests/grass_charset.rs new file mode 100644 index 000000000000..8dfe8cabc252 --- /dev/null +++ b/css/parser/tests/grass_charset.rs @@ -0,0 +1,18 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + utf8_input, + "a {\n color: 🦆;\n}\n", + "@charset \"UTF-8\";\na {\n color: 🦆;\n}\n" +); +grass_test!( + ascii_charset_utf8, + "@charset \"UTF-8\";\na {\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + unknown_charset, + "@charset \"foo\";\na {\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); diff --git a/css/parser/tests/grass_color.rs b/css/parser/tests/grass_color.rs new file mode 100644 index 000000000000..c2c01e783a63 --- /dev/null +++ b/css/parser/tests/grass_color.rs @@ -0,0 +1,665 @@ +#[macro_use] +mod grass_macros; + +grass_test!(preserves_named_color_case, "a {\n color: OrAnGe;\n}\n"); +grass_test!( + named_color_casing_is_color, + "a {\n color: hue(RED);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!(preserves_hex_color_case, "a {\n color: #FfFfFf;\n}\n"); +grass_test!( + preserves_hex_8_val_10000000, + "a {\n color: #10000000;\n}\n" +); +grass_test!( + preserves_hex_8_val_12312312, + "a {\n color: #12312312;\n}\n" +); +grass_test!( + preserves_hex_8_val_ab234cff, + "a {\n color: #ab234cff;\n}\n" +); +grass_test!(preserves_hex_6_val_000000, "a {\n color: #000000;\n}\n"); +grass_test!(preserves_hex_6_val_123123, "a {\n color: #123123;\n}\n"); +grass_test!(preserves_hex_6_val_ab234c, "a {\n color: #ab234c;\n}\n"); +grass_test!(preserves_hex_4_val_0000, "a {\n color: #0000;\n}\n"); +grass_test!(preserves_hex_4_val_123a, "a {\n color: #123a;\n}\n"); +grass_test!(preserves_hex_4_val_ab2f, "a {\n color: #ab2f;\n}\n"); +grass_test!(preserves_hex_3_val_000, "a {\n color: #000;\n}\n"); +grass_test!(preserves_hex_3_val_123, "a {\n color: #123;\n}\n"); +grass_test!(preserves_hex_3_val_ab2, "a {\n color: #ab2;\n}\n"); +grass_test!( + converts_rgb_to_named_color, + "a {\n color: rgb(0, 0, 0);\n}\n", + "a {\n color: black;\n}\n" +); +grass_test!( + converts_rgba_to_named_color_red, + "a {\n color: rgb(255, 0, 0, 255);\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + rgb_negative, + "a {\n color: rgb(-1, 1, 1);\n}\n", + "a {\n color: #000101;\n}\n" +); +grass_test!( + rgb_binop, + "a {\n color: rgb(1, 2, 1+2);\n}\n", + "a {\n color: #010203;\n}\n" +); +grass_test!( + rgb_pads_0, + "a {\n color: rgb(1, 2, 3);\n}\n", + "a {\n color: #010203;\n}\n" +); +grass_test!( + rgba_percent, + "a {\n color: rgba(159%, 169, 169%, 50%);\n}\n", + "a {\n color: rgba(255, 169, 255, 0.5);\n}\n" +); +grass_test!( + rgba_percent_round_up, + "a {\n color: rgba(59%, 169, 69%, 50%);\n}\n", + "a {\n color: rgba(150, 169, 176, 0.5);\n}\n" +); +grass_test!( + rgb_double_digits, + "a {\n color: rgb(254, 255, 255);\n}\n", + "a {\n color: #feffff;\n}\n" +); +grass_test!( + rgb_double_digits_white, + "a {\n color: rgb(255, 255, 255);\n}\n", + "a {\n color: white;\n}\n" +); +grass_test!( + alpha_function_4_hex, + "a {\n color: alpha(#0123);\n}\n", + "a {\n color: 0.2;\n}\n" +); +grass_test!( + alpha_function_named_color, + "a {\n color: alpha(red);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!(opacity_function_number, "a {\n color: opacity(1);\n}\n"); +grass_test!( + opacity_function_number_unit, + "a {\n color: opacity(1px);\n}\n" +); +grass_test!( + rgba_one_arg, + "a {\n color: rgba(1 2 3);\n}\n", + "a {\n color: #010203;\n}\n" +); +grass_test!( + rgb_two_args, + "a {\n color: rgb(#123, 0);\n}\n", + "a {\n color: rgba(17, 34, 51, 0);\n}\n" +); +grass_test!( + rgba_two_args, + "a {\n color: rgba(red, 0.5);\n}\n", + "a {\n color: rgba(255, 0, 0, 0.5);\n}\n" +); +grass_test!( + rgba_opacity_over_1, + "a {\n color: rgba(1, 2, 3, 3);\n}\n", + "a {\n color: #010203;\n}\n" +); +grass_test!( + rgba_negative_alpha, + "a {\n color: rgba(1, 2, 3, -10%);\n}\n", + "a {\n color: rgba(1, 2, 3, 0);\n}\n" +); +grass_test!( + rgba_opacity_decimal, + "a {\n color: rgba(1, 2, 3, .6);\n}\n", + "a {\n color: rgba(1, 2, 3, 0.6);\n}\n" +); +grass_test!( + rgba_opacity_percent, + "a {\n color: rgba(1, 2, 3, 50%);\n}\n", + "a {\n color: rgba(1, 2, 3, 0.5);\n}\n" +); +grass_test!( + rgba_3_args, + "a {\n color: rgba(7.1%, 20.4%, 33.9%);\n}\n", + "a {\n color: #123456;\n}\n" +); +grass_error!( + rgb_no_args, + "a {\n color: rgb();\n}\n", + "Error: Missing argument $channels." +); +grass_error!( + rgba_no_args, + "a {\n color: rgba();\n}\n", + "Error: Missing argument $channels." +); +grass_error!( + hsl_no_args, + "a {\n color: hsl();\n}\n", + "Error: Missing argument $channels." +); +grass_error!( + hsla_no_args, + "a {\n color: hsla();\n}\n", + "Error: Missing argument $channels." +); +grass_test!( + hsl_basic, + "a {\n color: hsl(193, 67%, 99);\n}\n", + "a {\n color: #fbfdfe;\n}\n" +); +grass_test!( + hsla_basic, + "a {\n color: hsla(193, 67%, 99, .6);\n}\n", + "a {\n color: rgba(251, 253, 254, 0.6);\n}\n" +); +grass_test!( + hsl_doesnt_care_about_units, + "a {\n color: hsl(193deg, 67foo, 99%);\n}\n", + "a {\n color: #fbfdfe;\n}\n" +); +grass_test!( + hsl_named, + "a {\n color: hsl($hue: 193, $saturation: 67%, $lightness: 99);\n}\n", + "a {\n color: #fbfdfe;\n}\n" +); +grass_test!( + hsl_four_args, + "a {\n color: hsl(0, 0, 0, 0.456);\n}\n", + "a {\n color: rgba(0, 0, 0, 0.456);\n}\n" +); +grass_test!( + hsl_negative_hue, + "a {\n color: hsl(-60deg, 100%, 50%);\n}\n", + "a {\n color: fuchsia;\n}\n" +); +grass_test!( + hsl_hue_above_max, + "a {\n color: hsl(540, 100%, 50%);\n}\n", + "a {\n color: aqua;\n}\n" +); +grass_test!( + hsl_hue_below_min, + "a {\n color: hsl(-540, 100%, 50%);\n}\n", + "a {\n color: aqua;\n}\n" +); +grass_test!( + hsla_named, + "a {\n color: hsla($hue: 193, $saturation: 67%, $lightness: 99, $alpha: .6);\n}\n", + "a {\n color: rgba(251, 253, 254, 0.6);\n}\n" +); +grass_test!( + hue, + "a {\n color: hue(hsl(193, 67%, 28%));\n}\n", + "a {\n color: 193deg;\n}\n" +); +grass_test!( + hue_maintains_value_when_created_through_hsl, + "a {\n color: hue(hsl(0.544, 100%, 100%));\n}\n", + "a {\n color: 0.544deg;\n}\n" +); +grass_test!( + hue_red_equals_blue, + "a {\n color: hue(rgb(1, 0, 1));\n}\n", + "a {\n color: 300deg;\n}\n" +); +grass_test!( + hue_of_360_becomes_0, + "a {\n color: hue(hsl(360, 10%, 20%));\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + hue_green_equals_blue, + "a {\n color: hue(rgb(0, 1, 1));\n}\n", + "a {\n color: 180deg;\n}\n" +); +grass_test!( + hue_green_is_1, + "a {\n color: hue(rgb(0, 1, 0));\n}\n", + "a {\n color: 120deg;\n}\n" +); +grass_test!( + hue_rgb_all_equal, + "a {\n color: hue(rgb(1, 1, 1));\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + saturation, + "a {\n color: saturation(hsl(193, 67%, 28%));\n}\n", + "a {\n color: 67%;\n}\n" +); +grass_test!( + saturation_2, + "$a: hsl(1, 1, 10);\n\na {\n color: saturation($a);\n}\n", + "a {\n color: 1%;\n}\n" +); +grass_test!( + lightness, + "a {\n color: lightness(hsl(193, 67%, 28%));\n}\n", + "a {\n color: 28%;\n}\n" +); +grass_test!( + invert_no_weight, + "a {\n color: invert(white);\n}\n", + "a {\n color: black;\n}\n" +); +grass_test!(invert_number, "a {\n color: invert(10%);\n}\n"); +grass_test!(invert_number_casing, "a {\n color: iNveRt(10%);\n}\n"); +grass_test!( + invert_weight_percent, + "a {\n color: invert(white, 20%);\n}\n", + "a {\n color: #cccccc;\n}\n" +); +grass_test!( + invert_weight_percent_turquoise, + "a {\n color: invert(turquoise, 23%);\n}\n", + "a {\n color: #5db4ab;\n}\n" +); +grass_test!( + invert_weight_no_unit, + "a {\n color: invert(white, 20);\n}\n", + "a {\n color: #cccccc;\n}\n" +); +grass_test!( + adjust_hue_positive, + "a {\n color: adjust-hue(hsl(120, 30%, 90%), 60deg);\n}\n", + "a {\n color: #deeded;\n}\n" +); +grass_test!( + adjust_hue_negative, + "a {\n color: adjust-hue(hsl(120, 30%, 90%), -60deg);\n}\n", + "a {\n color: #ededde;\n}\n" +); +grass_test!( + adjust_hue_3_hex, + "a {\n color: adjust-hue(#811, 45deg);\n}\n", + "a {\n color: #886a11;\n}\n" +); +grass_test!( + adjust_hue_named_args, + "a {\n color: adjust-hue($color: hsl(120, 30%, 90%), $degrees: 60deg);\n}\n", + "a {\n color: #deeded;\n}\n" +); +grass_test!( + lighten_named_args, + "a {\n color: lighten($color: hsl(0, 0%, 0%), $amount: 30%);\n}\n", + "a {\n color: #4d4d4d;\n}\n" +); +grass_test!( + lighten_basic, + "a {\n color: lighten(hsl(0, 0%, 0%), 30%);\n}\n", + "a {\n color: #4d4d4d;\n}\n" +); +grass_test!( + lighten_3_hex, + "a {\n color: lighten(#800, 20%);\n}\n", + // eventually, this should become `#e00` + // blocked on recognizing when to use 3-hex over 6-hex + "a {\n color: #ee0000;\n}\n" +); +grass_test!( + darken_named_args, + "a {\n color: darken($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n", + "a {\n color: #ff6a00;\n}\n" +); +grass_test!( + darken_basic, + "a {\n color: darken(hsl(25, 100%, 80%), 30%);\n}\n", + "a {\n color: #ff6a00;\n}\n" +); +grass_test!( + darken_3_hex, + "a {\n color: darken(#800, 20%);\n}\n", + // eventually, this should become `#200` + // blocked on recognizing when to use 3-hex over 6-hex + "a {\n color: #220000;\n}\n" +); +grass_test!( + saturate_named_args, + "a {\n color: saturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n", + "a {\n color: #ffc499;\n}\n" +); +grass_test!( + saturate_one_arg, + "a {\n color: saturate($amount: 50%);\n}\n", + "a {\n color: saturate(50%);\n}\n" +); +grass_test!( + saturate_basic, + "a {\n color: saturate(hsl(120, 30%, 90%), 20%);\n}\n", + "a {\n color: #d9f2d9;\n}\n" +); +grass_test!( + saturate_3_hex, + "a {\n color: saturate(#855, 20%);\n}\n", + "a {\n color: #9e3f3f;\n}\n" +); +grass_test!( + desaturate_named_args, + "a {\n color: desaturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n", + "a {\n color: #f0c6a8;\n}\n" +); +grass_test!( + desaturate_basic, + "a {\n color: desaturate(hsl(120, 30%, 90%), 20%);\n}\n", + "a {\n color: #e3e8e3;\n}\n" +); +grass_test!( + desaturate_3_hex, + "a {\n color: desaturate(#855, 20%);\n}\n", + "a {\n color: #726b6b;\n}\n" +); +grass_test!( + desaturate_correctly_calculates_hue, + "a {\n color: desaturate(plum, 14%);\n}\n", + "a {\n color: #d4a9d4;\n}\n" +); +grass_test!( + transparentize, + "a {\n color: transparentize(rgba(0, 0, 0, 0.5), 0.1);\n}\n", + "a {\n color: rgba(0, 0, 0, 0.4);\n}\n" +); +grass_test!( + fade_out, + "a {\n color: fade-out(rgba(0, 0, 0, 0.8), 0.2);\n}\n", + "a {\n color: rgba(0, 0, 0, 0.6);\n}\n" +); +grass_test!( + opacify, + "a {\n color: opacify(rgba(0, 0, 0, 0.5), 0.1);\n}\n", + "a {\n color: rgba(0, 0, 0, 0.6);\n}\n" +); +grass_test!( + fade_in, + "a {\n color: opacify(rgba(0, 0, 17, 0.8), 0.2);\n}\n", + "a {\n color: #000011;\n}\n" +); +grass_test!( + grayscale_1, + "a {\n color: grayscale(plum);\n}\n", + "a {\n color: #bfbfbf;\n}\n" +); +grass_test!( + grayscale_2, + "a {\n color: grayscale(red);\n}\n", + "a {\n color: gray;\n}\n" +); +grass_test!( + grayscale_number, + "a {\n color: grayscale(15%);\n}\n", + "a {\n color: grayscale(15%);\n}\n" +); +grass_test!( + complement, + "a {\n color: complement(red);\n}\n", + "a {\n color: aqua;\n}\n" +); +grass_test!( + complement_hue_under_180, + "a {\n color: complement(#abcdef);\n}\n", + "a {\n color: #efcdab;\n}\n" +); +grass_test!( + mix_no_weight, + "a {\n color: mix(#f00, #00f);\n}\n", + "a {\n color: purple;\n}\n" +); +grass_test!( + mix_weight_25, + "a {\n color: mix(#f00, #00f, 25%);\n}\n", + "a {\n color: #4000bf;\n}\n" +); +grass_test!( + mix_opacity, + "a {\n color: mix(rgba(255, 0, 0, 0.5), #00f);\n}\n", + "a {\n color: rgba(64, 0, 191, 0.75);\n}\n" +); +grass_test!( + mix_sanity_check, + "a {\n color: mix(black, white);\n}\n", + "a {\n color: gray;\n}\n" +); +grass_test!( + change_color_blue, + "a {\n color: change-color(#102030, $blue: 5);\n}\n", + "a {\n color: #102005;\n}\n" +); +grass_test!( + change_color_red_blue, + "a {\n color: change-color(#102030, $red: 120, $blue: 5);\n}\n", + "a {\n color: #782005;\n}\n" +); +grass_test!( + change_color_lum_alpha, + "a {\n color: change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8);\n}\n", + "a {\n color: rgba(204, 85, 0, 0.8);\n}\n" +); +grass_test!( + adjust_color_blue, + "a {\n color: adjust-color(#102030, $blue: 5);\n}\n", + "a {\n color: #102035;\n}\n" +); +grass_test!( + adjust_color_negative, + "a {\n color: adjust-color(#102030, $red: -5, $blue: 5);\n}\n", + "a {\n color: #0b2035;\n}\n" +); +grass_test!( + adjust_color_lum_alpha, + "a {\n color: adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4);\n}\n", + "a {\n color: rgba(255, 106, 0, 0.6);\n}\n" +); +grass_test!( + scale_color_lightness, + "a {\n color: scale-color(hsl(120, 70%, 80%), $lightness: 50%);\n}\n", + "a {\n color: #d4f7d4;\n}\n" +); +grass_test!( + scale_color_negative, + "a {\n color: scale-color(rgb(200, 150%, 170%), $green: -40%, $blue: 70%);\n}\n", + "a {\n color: #c899ff;\n}\n" +); +grass_test!( + scale_color_alpha, + "a {\n color: scale-color(hsl(200, 70%, 80%), $saturation: -90%, $alpha: -30%);\n}\n", + "a {\n color: rgba(200, 205, 208, 0.7);\n}\n" +); +grass_test!( + scale_color_alpha_over_1, + "a {\n color: scale-color(sienna, $alpha: -70%);\n}\n", + "a {\n color: rgba(160, 82, 45, 0.3);\n}\n" +); +grass_test!( + ie_hex_str_hex_3, + "a {\n color: ie-hex-str(#abc);\n}\n", + "a {\n color: #FFAABBCC;\n}\n" +); +grass_test!( + ie_hex_str_hex_6, + "a {\n color: ie-hex-str(#3322BB);\n}\n", + "a {\n color: #FF3322BB;\n}\n" +); +grass_test!( + ie_hex_str_rgb, + "a {\n color: ie-hex-str(rgba(0, 255, 0, 0.5));\n}\n", + "a {\n color: #8000FF00;\n}\n" +); +grass_test!( + rgba_1_arg, + "a {\n color: rgba(74.7% 173 93%);\n}\n", + "a {\n color: #beaded;\n}\n" +); +grass_test!( + hsla_1_arg, + "a {\n color: hsla(60 60% 50%);\n}\n", + "a {\n color: #cccc33;\n}\n" +); +grass_test!( + hsla_1_arg_weird_units, + "a {\n color: hsla(60foo 60foo 50foo);\n}\n", + "a {\n color: #cccc33;\n}\n" +); +grass_test!( + sass_spec__spec_colors_basic, + "p { + color: rgb(255, 128, 0); + color: red green blue; + color: (red) (green) (blue); + color: red + hux; + color: unquote(\"red\") + green; + foo: rgb(200, 150%, 170%); +} +", + "p {\n color: #ff8000;\n color: red green blue;\n color: red green blue;\n color: \ + redhux;\n color: redgreen;\n foo: #c8ffff;\n}\n" +); +grass_test!( + sass_spec__spec_colors_change_color, + "p { + color: change-color(#102030, $blue: 5); + color: change-color(#102030, $alpha: .325); + color: change-color(#102030, $red: 120, $blue: 5); + color: change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8); +} +", + "p {\n color: #102005;\n color: rgba(16, 32, 48, 0.325);\n color: #782005;\n color: \ + rgba(204, 85, 0, 0.8);\n}\n" +); +grass_test!( + transparent_from_function, + "a {\n color: rgb(transparent, 0);\n}\n", + "a {\n color: rgba(0, 0, 0, 0);\n}\n" +); +grass_test!( + named_color_transparent_opacity, + "a {\n color: opacity(transparent);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + negative_values_in_rgb, + "a {\n color: rgb(-1 -1 -1);\n}\n", + "a {\n color: black;\n}\n" +); +grass_test!( + negative_values_in_hsl, + "a {\n color: hsl(-1 -1 -1);\n}\n", + "a {\n color: black;\n}\n" +); +grass_test!( + interpolation_after_hash_containing_only_hex_chars, + "a {\n color: ##{123};\n color: type-of(##{123});\n}\n", + "a {\n color: #123;\n color: string;\n}\n" +); +grass_test!( + non_hex_chars_after_hash_are_still_touching_hash, + "a {\n color: #ooobar;\n}\n", + "a {\n color: #ooobar;\n}\n" +); +grass_test!( + more_than_8_hex_chars_after_hash_starts_with_letter, + "a {\n color: #ffffffffff;\n}\n", + "a {\n color: #ffffffffff;\n}\n" +); +grass_test!( + more_than_8_hex_chars_after_hash_starts_with_number, + "a {\n color: #0000000000;\n}\n", + "a {\n color: #00000000 0;\n}\n" +); +grass_test!( + more_than_8_hex_chars_after_hash_starts_with_number_contains_hex_char, + "a {\n color: #00000000f00;\n}\n", + "a {\n color: #00000000 f00;\n}\n" +); +grass_test!( + all_three_rgb_channels_have_decimal, + "a {\n color: rgba(1.5, 1.5, 1.5, 1);\n}\n", + "a {\n color: #020202;\n}\n" +); +grass_test!( + builtin_fn_red_rounds_channel, + "a {\n color: red(rgba(1.5, 1.5, 1.5, 1));\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + builtin_fn_green_rounds_channel, + "a {\n color: green(rgba(1.5, 1.5, 1.5, 1));\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + builtin_fn_blue_rounds_channel, + "a {\n color: blue(rgba(1.5, 1.5, 1.5, 1));\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + color_equality_named_and_hex, + "a {\n color: red==#ff0000;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + color_equality_named_and_hsla, + "a {\n color: hsla(0deg, 100%, 50%)==red;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + hsla_becomes_named_color, + "a {\n color: hsla(0deg, 100%, 50%);\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + alpha_filter_one_arg, + "a {\n color: alpha(a=a);\n}\n", + "a {\n color: alpha(a=a);\n}\n" +); +grass_test!( + alpha_filter_multiple_args, + "a {\n color: alpha(a=a, b=b, c=d, d=d);\n}\n", + "a {\n color: alpha(a=a, b=b, c=d, d=d);\n}\n" +); +grass_test!( + alpha_filter_whitespace, + "a {\n color: alpha(a = a);\n}\n", + "a {\n color: alpha(a=a);\n}\n" +); +grass_test!( + alpha_filter_named, + "a {\n color: alpha($color: a=a);\n}\n", + "a {\n color: alpha(a=a);\n}\n" +); +grass_error!( + alpha_filter_both_null, + "a {\n color: alpha(null=null);\n}\n", + "Error: $color: = is not a color." +); +grass_error!( + alpha_filter_multiple_args_one_not_valid_filter, + "a {\n color: alpha(a=a, b);\n}\n", + "Error: Only 1 argument allowed, but 2 were passed." +); +grass_error!( + alpha_filter_invalid_from_whitespace, + "a {\n color: alpha( A a = a );\n}\n", + "Error: $color: A a=a is not a color." +); +grass_error!( + alpha_filter_invalid_non_alphabetic_start, + "a {\n color: alpha(1=a);\n}\n", + "Error: $color: 1=a is not a color." +); +// todo: we need many more of these tests +grass_test!( + rgba_special_fn_4th_arg_max, + "a {\n color: rgba(1 2 max(3, 3));\n}\n", + "a {\n color: rgba(1, 2, max(3, 3));\n}\n" +); +grass_test!( + #[ignore = "we do not check if interpolation occurred"] + interpolated_named_color_is_not_color, + "a {\n color: type-of(r#{e}d);\n}\n", + "a {\n color: string;\n}\n" +); diff --git a/css/parser/tests/grass_comments.rs b/css/parser/tests/grass_comments.rs new file mode 100644 index 000000000000..12f84e1cc2d0 --- /dev/null +++ b/css/parser/tests/grass_comments.rs @@ -0,0 +1,63 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + removes_inner_comments, + "a {\n color: red/* hi */;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + removes_inner_comments_whitespace, + "a {\n color: red /* hi */;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + preserves_outer_comments_before, + "a {\n /* hi */\n color: red;\n}\n" +); +grass_test!( + preserves_outer_comments_after, + "a {\n color: red;\n /* hi */\n}\n" +); +grass_test!( + preserves_outer_comments_two, + "a {\n /* foo */\n /* bar */\n color: red;\n}\n" +); +grass_test!( + preserves_toplevel_comment_before, + "/* foo */\na {\n color: red;\n}\n" +); +grass_test!( + preserves_toplevel_comment_after, + "a {\n color: red;\n}\n/* foo */\n" +); +grass_test!( + removes_single_line_comment, + "// a { color: red }\na {\n height: 1 1px;\n}\n", + "a {\n height: 1 1px;\n}\n" +); +grass_test!( + converts_form_feed_in_comment, + "a {\n /* \x0C*/ color: red;\n}\n", + "a {\n /* \n*/\n color: red;\n}\n" +); +grass_test!( + converts_crlf_in_comment, + "a {\n /* \r\n*/ color: red;\n}\n", + "a {\n /* \n*/\n color: red;\n}\n" +); +grass_test!( + closing_curly_brace_in_comment, + "a {\n color: 1 + // flang }\n blang }", + "a {\n color: 1blang;\n}\n" +); +grass_test!( + converts_cr_in_comment, + "a {\n /* \r*/ color: red;\n}\n", + "a {\n /* \n*/\n color: red;\n}\n" +); +grass_test!( + interpolation_in_multiline_comment, + "$a: foo;/* interpolation #{1 + 1} in #{$a} comments */", + "/* interpolation 2 in foo comments */\n" +); diff --git a/css/parser/tests/grass_content_exists.rs b/css/parser/tests/grass_content_exists.rs new file mode 100644 index 000000000000..57b23b1cb412 --- /dev/null +++ b/css/parser/tests/grass_content_exists.rs @@ -0,0 +1,57 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + outside_mixin, + "a {\n color: content-exists();\n}\n", + "Error: content-exists() may only be called within a mixin." +); +grass_test!( + include_no_braces_no_args, + "@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + include_no_braces_empty_args, + "@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo();\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + include_empty_braces_no_args, + "@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{};\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + include_style_inside_braces_no_args, + "@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{color: \ + red;};\n}\n", + "a {\n color: true;\n color: red;\n}\n" +); +grass_test!( + include_style_inside_braces_missing_semicolon_no_args, + "@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{color: \ + red};\n}\n", + "a {\n color: true;\n color: red;\n}\n" +); +grass_test!( + chained_mixin_second_doesnt_have_content, + "@mixin foo { + color: content-exists(); + } + + @mixin bar { + @include foo; + @content; + } + + a { + @include bar {} + }", + "a {\n color: false;\n}\n" +); +grass_error!( + #[ignore = "haven't yet figured out a good way to check for whether an @content block exists"] + include_empty_braces_no_args_no_at_content, + "@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo{};\n}\n", + "Error: Mixin doesn't accept a content block." +); diff --git a/css/parser/tests/grass_division.rs b/css/parser/tests/grass_division.rs new file mode 100644 index 000000000000..8af82376aead --- /dev/null +++ b/css/parser/tests/grass_division.rs @@ -0,0 +1,176 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + none_div_none, + "a {\n color: (35 / 7);\n}\n", + "a {\n color: 5;\n}\n" +); +grass_test!( + unit_div_none, + "a {\n color: (35% / 7);\n}\n", + "a {\n color: 5%;\n}\n" +); +grass_test!( + unit_div_unit, + "a {\n color: (35% / 7%);\n}\n", + "a {\n color: 5;\n}\n" +); +grass_test!( + unit_conversion, + "a {\n color: (35px / 7in);\n}\n", + "a {\n color: 0.0520833333;\n}\n" +); +grass_test!( + slash_after_comma, + "a {\n slash-after-comma: (1, / 2);\n}\n", + "a {\n slash-after-comma: 1, /2;\n}\n" +); +grass_test!( + num_div_space_list, + "a {\n color: 1 / (a b);\n}\n", + "a {\n color: 1/a b;\n}\n" +); +grass_test!( + num_div_comma_list, + "a {\n color: 1 / (a, b);\n}\n", + "a {\n color: 1/a, b;\n}\n" +); +grass_test!( + num_div_true, + "a {\n color: 1 / true;\n}\n", + "a {\n color: 1/true;\n}\n" +); +grass_test!( + num_div_false, + "a {\n color: 1 / false;\n}\n", + "a {\n color: 1/false;\n}\n" +); +grass_test!( + num_div_important, + "a {\n color: 1 / !important;\n}\n", + "a {\n color: 1/!important;\n}\n" +); +grass_test!( + num_div_null, + "a {\n color: 1 / null;\n}\n", + "a {\n color: 1/;\n}\n" +); +grass_test!( + num_div_named_color, + "a {\n color: 1 / red;\n}\n", + "a {\n color: 1/red;\n}\n" +); +grass_test!( + dblquoted_string_div_space_separated_list, + "a {\n color: \"foo\"/(a b);\n}\n", + "a {\n color: \"foo\"/a b;\n}\n" +); +grass_test!( + null_div_number, + "a {\n color: null / 1;\n}\n", + "a {\n color: /1;\n}\n" +); +grass_test!( + null_div_dblquoted_string, + "a {\n color: null / \"foo\";\n}\n", + "a {\n color: /\"foo\";\n}\n" +); +grass_test!( + number_div_arglist, + "@function foo($a...) { + @return 1 / $a; + } + + a { + color: foo(a, b); + }", + "a {\n color: 1/a, b;\n}\n" +); +grass_test!( + string_div_arglist, + "@function foo($a...) { + @return foo / $a; + } + + a { + color: foo(a, b); + }", + "a {\n color: foo/a, b;\n}\n" +); +grass_error!( + string_div_map, + "a {\n color: foo / (a: b);\n}\n", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + string_div_function, + "a {\n color: foo / get-function(lighten);\n}\n", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_error!( + num_div_map, + "a {\n color: 1 / (a: b);\n}\n", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + num_div_function, + "a {\n color: 1 / get-function(lighten);\n}\n", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_test!( + does_not_eval_plain, + "a {\n color: 1 / 2;\n}\n", + "a {\n color: 1/2;\n}\n" +); +grass_test!( + does_eval_inside_parens, + "a {\n color: (1/2);\n}\n", + "a {\n color: 0.5;\n}\n" +); +grass_test!( + does_eval_when_one_is_calculated, + "a {\n color: (1*1) / 2;\n}\n", + "a {\n color: 0.5;\n}\n" +); +grass_test!( + does_not_eval_from_unary_minus, + "a {\n color: -1 / 2;\n}\n", + "a {\n color: -1/2;\n}\n" +); +grass_test!( + does_eval_from_variable, + "$a: 1;\na {\n color: $a / 2;\n}\n", + "a {\n color: 0.5;\n}\n" +); +grass_test!( + does_eval_single_number_in_parens, + "a {\n color: (1) / 2;\n}\n", + "a {\n color: 0.5;\n}\n" +); +grass_test!( + does_eval_function_call, + "@function foo() { + @return 1; + } + + a { + color: foo() / 2; + }", + "a {\n color: 0.5;\n}\n" +); +grass_test!( + does_not_eval_chained_binop_division, + "a {\n color: 1 / 3 / 4;\n}\n", + "a {\n color: 1/3/4;\n}\n" +); +grass_test!( + does_not_eval_chained_binop_one_not_division, + "a {\n color: 1 + 3 / 4;\n}\n", + "a {\n color: 1.75;\n}\n" +); +grass_test!( + zero_div_zero_is_nan, + "a {\n color: (0 / 0);\n}\n", + "a {\n color: NaN;\n}\n" +); diff --git a/css/parser/tests/grass_each.rs b/css/parser/tests/grass_each.rs new file mode 100644 index 000000000000..9a55d7349afe --- /dev/null +++ b/css/parser/tests/grass_each.rs @@ -0,0 +1,103 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + each_space_separated_inner, + "a {\n @each $i in 1 2 3 {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n color: 2;\n color: 3;\n}\n" +); +grass_test!( + each_comma_separated_inner, + "a {\n @each $i in 1, 2, 3 {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n color: 2;\n color: 3;\n}\n" +); +grass_test!( + each_space_separated_outer, + "@each $i in 1 2 3 {\n a {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n}\n\na {\n color: 2;\n}\n\na {\n color: 3;\n}\n" +); +grass_test!( + each_two_variables_one_null, + "a {\n @each $i, $c in 1 2 3 {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n color: 2;\n color: 3;\n}\n" +); +grass_test!( + each_one_var_in_one_map, + "a {\n @each $i in (a: b) {\n color: $i;\n }\n}\n", + "a {\n color: a b;\n}\n" +); +grass_test!( + each_two_vars_in_one_map, + "a {\n @each $i, $c in (a: b) {\n color: $i;\n }\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + each_two_vars_in_3_2_list, + "a {\n @each $i, $c in (1 2 3, 4 5) {\n color: $i, $c;\n }\n}\n", + "a {\n color: 1, 2;\n color: 4, 5;\n}\n" +); +grass_test!( + each_paren_space_separated, + "a {\n @each $i in (1 2 3) {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n color: 2;\n color: 3;\n}\n" +); +grass_test!( + type_of_each_space_separated_single_var, + "a {\n @each $i in 1 2 3 {\n color: type-of($i);\n }\n}\n", + "a {\n color: number;\n color: number;\n color: number;\n}\n" +); +grass_test!( + list_of_single_map_with_multiple_elements, + "a { + $settings: (); + + @each $config in [(a: b, c: d)] { + $settings: map-merge($settings, $config); + } + + color: inspect($settings); + }", + "a {\n color: (a: b, c: d);\n}\n" +); +grass_test!( + indexing_variable_does_not_affect_outer_scopes, + "a { + $a: 1; + $b: 1; + + @each $a in a b { + color: $a; + $b: $a; + } + + color: $a; + color: $b; + }", + "a {\n color: a;\n color: b;\n color: 1;\n color: b;\n}\n" +); +// todo: newlines are not correct +grass_test!( + multiline_comments_everywhere, + " /**/ @each /**/ $a /**/ , /**/ $b /**/ in /**/ ( /**/ a /**/ , /**/ b \ + /**/ ) /**/ { /**/ + a { + color: $a; + color: $b; + } + } /**/ ", + "/**/\n/**/\na {\n color: a;\n}\n/**/\n\na {\n color: b;\n}\n/**/\n" +); +grass_error!( + list_of_single_map, + "a { + @each $a in [(a: b)] { + color: $a; + } + }", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + missing_closing_curly_brace, + "@each $i in 1 {", + "Error: expected \"}\"." +); diff --git a/css/parser/tests/grass_equality.rs b/css/parser/tests/grass_equality.rs new file mode 100644 index 000000000000..6376829f3259 --- /dev/null +++ b/css/parser/tests/grass_equality.rs @@ -0,0 +1,63 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + color_equals_color, + "a {\n color: red == red;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + color_does_not_equal_color, + "a {\n color: red != red;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + unquoted_ident_eq_unquoted_ident, + "a {\n color: foo == foo;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + dblquoted_ident_eq_unquoted_ident, + "a {\n color: \"foo\" == foo;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + dblquoted_ident_eq_sglquoted_ident, + "a {\n color: \"foo\" == 'foo';\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + dblquoted_eq_number, + "a {\n color: \"foo\" == 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + uncomparable_units, + "a {\n color: 1rem==1px;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + first_unit_none, + "a {\n color: 1==1px;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + second_unit_none, + "a {\n color: 1rem==1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + different_quoting_inside_list_eq, + "a {\n color: (\"foo\",) == (foo,);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + different_quoting_inside_list_ne, + "a {\n color: (\"foo\",) != (foo,);\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + map_keys_equivalent, + "a {\n color: (0mm: a)==(0cm: a);\n}\n", + "a {\n color: true;\n}\n" +); diff --git a/css/parser/tests/grass_error.rs b/css/parser/tests/grass_error.rs new file mode 100644 index 000000000000..8b4525e68406 --- /dev/null +++ b/css/parser/tests/grass_error.rs @@ -0,0 +1,312 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + nothing_after_decimal, + "a {color: 1.;}", + "Error: Expected digit." +); +grass_error!( + ascii_control_character, + "a {color: ;}", + "Error: Expected expression." +); +grass_error!( + toplevel_invalid_atrule_ident, + "@`or $i from 1 through 3 {}", + "Error: Expected identifier." +); +grass_error!( + return_as_style, + "a {@return foo;}", + "Error: This at-rule is not allowed here." +); +grass_error!( + colon_inside_value, + "a {foo: bar: baz;}", + "Error: expected \";\"." +); +grass_error!( + question_mark_inside_value, + "a {foo: bar?}", + "Error: expected \";\"." +); +grass_error!( + interpolation_in_variable_declaration, + "$base-#{lor}: #036;", + "Error: expected \":\"." +); +grass_error!( + backslash_as_last_character, + "a {colo\\: red;}", + "Error: expected \"{\"." +); +grass_error!( + close_paren_without_opening, + "a {color: foo);}", + "Error: expected \";\"." +); +grass_error!( + symbol_after_hash, + "a {color: bar + #}ar;}", + "Error: Expected identifier." +); +grass_error!( + control_character_starts_selector_toplevel, + "l {color: foo;}", + "Error: expected selector." +); +grass_error!( + control_character_starts_selector_inner, + "a{l {color: foo;}}", + "Error: expected selector." +); +grass_error!(backtick_in_selector, "a`{}", "Error: expected selector."); +grass_error!( + no_value_after_forward_slash, + "a {color: 303/;}", + "Error: Expected expression." +); +grass_error!(xor_in_value, "a {color: a^;}", "Error: expected \";\"."); +grass_error!( + nothing_after_at_sign, + "a {color: red; @", + "Error: Expected identifier." +); +grass_error!( + missing_colon_in_style, + "a {color, red;}", + "Error: expected \"{\"." +); +grass_error!( + toplevel_forward_slash, + "/a {color: red;}", + "Error: expected selector." +); +grass_error!( + close_bracket_in_value, + "a {color: red]}", + "Error: expected \";\"." +); +grass_error!( + no_ident_after_dollar_in_style, + "a {$", + "Error: Expected identifier." +); +grass_error!( + nothing_after_variable_in_style, + "a {$a", + "Error: expected \":\"." +); +grass_error!(toplevel_comma, "a {},", "Error: expected \"{\"."); +grass_error!(toplevel_exclamation_alone, "!", "Error: expected \"}\"."); +grass_error!(toplevel_exclamation, "! {}", "Error: expected \"}\"."); +grass_error!(toplevel_backtick, "` {}", "Error: expected selector."); +// note that the message dart-sass gives is: `Error: expected "}".` +grass_error!( + toplevel_open_curly_brace, + "{ {color: red;}", + "Error: expected \"}\"." +); +grass_error!(toplevel_open_paren, "(", "Error: expected \"{\"."); +grass_error!(toplevel_close_paren, ")", "Error: expected \"{\"."); +grass_error!( + backtick_in_value, + "a {color:`red;}", + "Error: Expected expression." +); +grass_error!( + comma_begins_value, + "a {color:,red;}", + "Error: Expected expression." +); +// dart-sass gives `Error: expected "{".` +grass_error!(nothing_after_hyphen, "a {-}", "Error: Expected identifier."); +grass_error!( + nothing_after_hyphen_variable, + "a {$-", + "Error: expected \":\"." +); +grass_error!( + closing_brace_after_hyphen_variable, + "a {$-}", + "Error: Expected identifier." +); +grass_error!( + dbl_quoted_selector, + "\"a\" {color: red;}", + "Error: expected selector." +); +grass_error!( + sgl_quoted_selector, + "'a' {color: red;}", + "Error: expected selector." +); +grass_error!( + toplevel_hash_no_closing_curly_brace_has_value, + "#{f", + "Error: expected \"}\"." +); +grass_error!( + toplevel_hash_no_closing_curly_brace_no_value, + "#{", + "Error: Expected expression." +); +grass_error!(toplevel_hash, "#", "Error: expected \"{\"."); +grass_error!( + #[ignore = "we use closing brace to end scope"] + toplevel_closing_brace, + "}", + "Error: unmatched \"}\"." +); +grass_error!(toplevel_at, "@", "Error: Expected identifier."); +grass_error!( + toplevel_ampersand, + "& {}", + "Error: Top-level selectors may not contain the parent selector \"&\"." +); +grass_error!(toplevel_backslash, "\\", "Error: expected \"{\"."); +grass_error!(toplevel_var_no_colon, "$r", "Error: expected \":\"."); +grass_error!(bar_in_value, "a {color: a|b;}", "Error: expected \";\"."); +grass_error!( + tilde_in_value, + "a {color: ~a;}", + "Error: Expected expression." +); +grass_error!( + subtract_rem, + "a {color: 5 - %;}", + "Error: Expected expression." +); +grass_error!( + operator_eq, + "a {color: 5 - ==;}", + "Error: Expected expression." +); +grass_error!( + operator_ne, + "a {color: 5 - !=;}", + "Error: Expected expression." +); +grass_error!( + operator_gt, + "a {color: 5 - >;}", + "Error: Expected expression." +); +grass_error!( + operator_lt, + "a {color: 5 - <;}", + "Error: Expected expression." +); +grass_error!( + operator_ge, + "a {color: 5 - >=;}", + "Error: Expected expression." +); +grass_error!( + operator_le, + "a {color: 5 - <=;}", + "Error: Expected expression." +); +grass_error!( + operator_mul, + "a {color: 5 - *;}", + "Error: Expected expression." +); +grass_error!( + ends_with_single_eq, + "a {color: 1 =", + "Error: expected \"=\"." +); +grass_error!( + nothing_after_gt, + "a {color: 1 >", + "Error: Expected expression." +); +grass_error!(toplevel_eq_alone, "=", "Error: expected \"{\"."); +grass_error!(toplevel_gt_alone, ">", "Error: expected \"{\"."); +grass_error!(toplevel_lt_alone, "<", "Error: expected \"{\"."); +grass_error!(toplevel_question_alone, "?", "Error: expected \"{\"."); +grass_error!(toplevel_caret_alone, "^", "Error: expected \"{\"."); +grass_test!(toplevel_gt_as_selector, "> {}", ""); +grass_test!(toplevel_tilde_as_selector, "~ {}", ""); +grass_error!(toplevel_lt_as_selector, "< {}", "Error: expected selector."); +grass_error!(toplevel_pipe, "| {}", "Error: Expected identifier."); +grass_error!( + toplevel_question_as_selector, + "? {}", + "Error: expected selector." +); +grass_error!( + toplevel_caret_as_selector, + "^ {}", + "Error: expected selector." +); +grass_error!(toplevel_eq, "= {}", "Error: expected selector."); +grass_error!(value_after_style, "a {}a", "Error: expected \"{\"."); +grass_test!(whitespace_after_style, "a {}\t\n ", ""); +grass_test!(toplevel_semicolon, ";", ""); +grass_test!(toplevel_semicolon_after_style, "a {};", ""); +grass_error!( + nothing_after_hash_in_interpolated_ident_body, + "a {color: foo#", + "Error: Expected identifier." +); +grass_error!( + at_else_alone, + "@else {}", + "Error: This at-rule is not allowed here." +); +grass_error!( + no_expression_for_variable, + "a {$color: {ed;}", + "Error: Expected expression." +); +grass_error!( + empty_style_value_no_semicolon, + "a {color:}", + "Error: Expected expression." +); +grass_error!( + empty_style_value_semicolon, + "a {color:;}", + "Error: Expected expression." +); +grass_error!( + ident_colon_closing_brace, + "r:}", + "Error: Expected expression." +); +grass_error!(dollar_sign_alone, "$", "Error: Expected identifier."); +grass_error!( + nothing_after_dbl_quote, + "a {color: \"", + "Error: Expected \"." +); +grass_error!(nothing_after_sgl_quote, "a {color: '", "Error: Expected '."); +grass_error!( + invalid_binop_in_list, + "a {color: foo % bar, baz;}", + "Error: Undefined operation \"foo % bar\"." +); +grass_error!( + improperly_terminated_nested_style, + "a {foo: {bar: red", + "Error: Expected identifier." +); +grass_error!(toplevel_nullbyte, "\u{0}", "Error: expected selector."); +grass_error!( + double_escaped_bang_at_toplevel, + "\\!\\!", + "Error: expected \"{\"." +); +grass_error!( + nothing_after_escape_inside_brackets, + "a { color: [\\", + "Error: Expected expression." +); +grass_error!( + unclosed_bracketed_list, + "a { color: [a", + "Error: expected \"]\"." +); diff --git a/css/parser/tests/grass_extend.rs b/css/parser/tests/grass_extend.rs new file mode 100644 index 000000000000..c34203abb2be --- /dev/null +++ b/css/parser/tests/grass_extend.rs @@ -0,0 +1,1911 @@ +#[macro_use] +mod grass_macros; + +grass_test!(empty_extend_self, "a { @extend a; }", ""); +grass_test!( + extend_self_with_styles, + "a {\n color: red;\n @extend a;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + list_extends_both_of_compound, + ".foo.bar { + a: b + } + + .x, .y { + @extend .foo, .bar; + } + ", + ".foo.bar, .x, .y {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_placed_second, + ".foo {a: b} + .bar {@extend .foo}", + ".foo, .bar {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_placed_first, + ".bar {@extend .foo} + .foo {a: b}", + ".foo, .bar {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_style_before_extend, + ".foo {a: b} + .bar {c: d; @extend .foo;}", + ".foo, .bar {\n a: b;\n}\n\n.bar {\n c: d;\n}\n" +); +grass_test!( + class_extends_class_style_after_extend, + ".foo {a: b} + .bar {@extend .foo; c: d;}", + ".foo, .bar {\n a: b;\n}\n\n.bar {\n c: d;\n}\n" +); +grass_test!( + class_extends_class_applies_to_multiple_extendees, + ".foo {a: b} + .bar {@extend .foo} + .blip .foo {c: d}", + ".foo, .bar {\n a: b;\n}\n\n.blip .foo, .blip .bar {\n c: d;\n}\n" +); +grass_test!( + class_extends_class_one_class_extends_multiple, + ".foo {a: b} + .bar {c: d} + .baz {@extend .foo; @extend .bar}", + ".foo, .baz {\n a: b;\n}\n\n.bar, .baz {\n c: d;\n}\n" +); +grass_test!( + class_extends_class_multiple_classes_extend_one, + ".foo {a: b} + .bar {@extend .foo} + .baz {@extend .bar} + .bip {@extend .bar} + ", + ".foo, .bar, .bip, .baz {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_all_parts_of_complex_selector_extended_by_one, + ".foo .bar {a: b} + .baz {@extend .foo; @extend .bar} + ", + ".foo .bar, .foo .baz, .baz .bar, .baz .baz {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_all_parts_of_compound_selector_extended_by_one, + ".foo.bar {a: b} + .baz {@extend .foo; @extend .bar} + ", + ".foo.bar, .baz {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_all_parts_of_complex_selector_extended_by_different, + ".foo .bar {a: b} + .baz {@extend .foo} + .bang {@extend .bar} + ", + ".foo .bar, .foo .bang, .baz .bar, .baz .bang {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_all_parts_of_compound_selector_extended_by_different, + ".foo.bar {a: b} + .baz {@extend .foo} + .bang {@extend .bar} + ", + ".foo.bar, .foo.bang, .bar.baz, .baz.bang {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_simple_selector_extended_chain, + ".foo {a: b} + .bar {@extend .foo} + .baz {@extend .bar} + .bip {@extend .bar} + ", + ".foo, .bar, .bip, .baz {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_interpolated, + ".foo {a: b} + .bar {@extend #{\".foo\"}} + ", + ".foo, .bar {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_target_child_of_complex, + ".foo .bar {a: b} + .baz {@extend .bar} + ", + ".foo .bar, .foo .baz {\n a: b;\n}\n" +); +grass_test!( + class_extends_class_target_parent_of_complex, + ".foo .bar {a: b} + .baz {@extend .foo} + ", + ".foo .bar, .baz .bar {\n a: b;\n}\n" +); +grass_test!( + class_unification_1, + "%-a .foo.bar {a: b} + .baz {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a .bar.baz {\n a: b;\n}\n" +); +grass_test!( + class_unification_2, + "%-a .foo.baz {a: b} + .baz {@extend .foo} -a {@extend %-a} + ", + "-a .baz {\n a: b;\n}\n" +); +grass_test!( + id_unification_1, + "%-a .foo.bar {a: b} + #baz {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a .bar#baz {\n a: b;\n}\n" +); +grass_test!( + id_unification_2, + "%-a .foo#baz {a: b} + #baz {@extend .foo} -a {@extend %-a} + ", + "-a #baz {\n a: b;\n}\n" +); +grass_test!( + universal_unification_simple_target_1, + "%-a .foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a .foo, -a * {\n a: b;\n}\n" +); +grass_test!( + universal_unification_simple_target_2, + "%-a .foo.bar {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a .bar {\n a: b;\n}\n" +); +grass_test!( + universal_unification_simple_target_3, + "%-a .foo.bar {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a .bar {\n a: b;\n}\n" +); +grass_test!( + universal_unification_simple_target_4, + "%-a .foo.bar {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a ns|*.bar {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_1, + "%-a *.foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a * {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_2, + "%-a *.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a * {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_3, + "%-a *|*.foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a *|*.foo, -a * {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_4, + "%-a *|*.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a *|* {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_5, + "%-a *.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a *.foo {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_6, + "%-a *|*.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a *|*.foo, -a ns|* {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_7, + "%-a ns|*.foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a ns|*.foo {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_8, + "%-a ns|*.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a ns|* {\n a: b;\n}\n" +); +grass_test!( + universal_unification_universal_target_without_namespace_9, + "%-a ns|*.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a ns|* {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_1, + "%-a a.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a a {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_2, + "%-a *|a.foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a *|a.foo, -a a {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_3, + "%-a *|a.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a *|a {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_4, + "%-a a.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a a.foo {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_5, + "%-a *|a.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a *|a.foo, -a ns|a {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_6, + "%-a ns|a.foo {a: b} + * {@extend .foo} -a {@extend %-a} + ", + "-a ns|a.foo {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_7, + "%-a ns|a.foo {a: b} + *|* {@extend .foo} -a {@extend %-a} + ", + "-a ns|a {\n a: b;\n}\n" +); +grass_test!( + universal_unification_element_target_without_namespace_8, + "%-a ns|a.foo {a: b} + ns|* {@extend .foo} -a {@extend %-a} + ", + "-a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_simple_target_1, + "%-a .foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a .foo, -a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_simple_target_2, + "%-a .foo.bar {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a a.bar {\n a: b;\n}\n" +); +grass_test!( + element_unification_simple_target_3, + "%-a .foo.bar {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a *|a.bar {\n a: b;\n}\n" +); +grass_test!( + element_unification_simple_target_4, + "%-a .foo.bar {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a .foo.bar, -a ns|a.bar {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_1, + "%-a *.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a *.foo, -a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_2, + "%-a *.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a *.foo, -a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_3, + "%-a *|*.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a *|*.foo, -a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_4, + "%-a *|*.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a *|*.foo, -a *|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_5, + "%-a *.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a *.foo {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_without_namespace_6, + "%-a *|*.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a *|*.foo, -a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_with_namespace_1, + "%-a ns|*.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a ns|*.foo {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_with_namespace_2, + "%-a ns|*.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a ns|*.foo, -a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_universal_with_namespace_3, + "%-a ns|*.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a ns|*.foo, -a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_1, + "%-a a.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_2, + "%-a a.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_3, + "%-a *|a.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a *|a.foo, -a a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_4, + "%-a *|a.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a *|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_5, + "%-a a.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a a.foo {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_without_namespace_6, + "%-a *|a.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a *|a.foo, -a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_with_namespace_1, + "%-a ns|a.foo {a: b} + a {@extend .foo} -a {@extend %-a} + ", + "-a ns|a.foo {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_with_namespace_2, + "%-a ns|a.foo {a: b} + *|a {@extend .foo} -a {@extend %-a} + ", + "-a ns|a {\n a: b;\n}\n" +); +grass_test!( + element_unification_element_with_namespace_3, + "%-a ns|a.foo {a: b} + ns|a {@extend .foo} -a {@extend %-a} + ", + "-a ns|a {\n a: b;\n}\n" +); +grass_test!( + attribute_unification_1, + "%-a [foo=bar].baz {a: b} + [foo=baz] {@extend .baz} -a {@extend %-a} + ", + "-a [foo=bar].baz, -a [foo=bar][foo=baz] {\n a: b;\n}\n" +); +grass_test!( + attribute_unification_2, + "%-a [foo=bar].baz {a: b} + [foo^=bar] {@extend .baz} -a {@extend %-a} + ", + "-a [foo=bar].baz, -a [foo=bar][foo^=bar] {\n a: b;\n}\n" +); +grass_test!( + attribute_unification_3, + "%-a [foo=bar].baz {a: b} + [foot=bar] {@extend .baz} -a {@extend %-a} + ", + "-a [foo=bar].baz, -a [foo=bar][foot=bar] {\n a: b;\n}\n" +); +grass_test!( + attribute_unification_4, + "%-a [foo=bar].baz {a: b} + [ns|foo=bar] {@extend .baz} -a {@extend %-a} + ", + "-a [foo=bar].baz, -a [foo=bar][ns|foo=bar] {\n a: b;\n}\n" +); +grass_test!( + attribute_unification_5, + "%-a %-a [foo=bar].bar {a: b} + [foo=bar] {@extend .bar} -a {@extend %-a} + ", + "-a -a [foo=bar] {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_1, + "%-a :foo.baz {a: b} + :foo(2n+1) {@extend .baz} -a {@extend %-a} + ", + "-a :foo.baz, -a :foo:foo(2n+1) {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_2, + "%-a :foo.baz {a: b} + ::foo {@extend .baz} -a {@extend %-a} + ", + "-a :foo.baz, -a :foo::foo {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_3, + "%-a ::foo.baz {a: b} + ::foo {@extend .baz} -a {@extend %-a} + ", + "-a ::foo {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_4, + "%-a ::foo(2n+1).baz {a: b} + ::foo(2n+1) {@extend .baz} -a {@extend %-a} + ", + "-a ::foo(2n+1) {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_5, + "%-a :foo.baz {a: b} + :bar {@extend .baz} -a {@extend %-a} + ", + "-a :foo.baz, -a :foo:bar {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_6, + "%-a .baz:foo {a: b} + :after {@extend .baz} -a {@extend %-a} + ", + "-a .baz:foo, -a :foo:after {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_7, + "%-a .baz:after {a: b} + :foo {@extend .baz} -a {@extend %-a} + ", + "-a .baz:after, -a :foo:after {\n a: b;\n}\n" +); +grass_test!( + pseudo_unification_8, + "%-a :foo.baz {a: b} + :foo {@extend .baz} -a {@extend %-a} + ", + "-a :foo {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_remains_at_end_of_selector_1, + ".foo::bar {a: b} + .baz {@extend .foo} + ", + ".foo::bar, .baz::bar {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_remains_at_end_of_selector_2, + "a.foo::bar {a: b} + .baz {@extend .foo} + ", + "a.foo::bar, a.baz::bar {\n a: b;\n}\n" +); +grass_test!( + pseudoclass_remains_at_end_of_selector_1, + ".foo:bar {a: b} + .baz {@extend .foo} + ", + ".foo:bar, .baz:bar {\n a: b;\n}\n" +); +grass_test!( + pseudoclass_remains_at_end_of_selector_2, + "a.foo:bar {a: b} + .baz {@extend .foo} + ", + "a.foo:bar, a.baz:bar {\n a: b;\n}\n" +); +grass_test!( + pseudoclass_not_remains_at_end_of_selector, + ".foo:not(.bar) {a: b} + .baz {@extend .foo} + ", + ".foo:not(.bar), .baz:not(.bar) {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_goes_lefter_than_pseudoclass_1, + ".foo::bar {a: b} + .baz:bang {@extend .foo} + ", + ".foo::bar, .baz:bang::bar {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_goes_lefter_than_pseudoclass_2, + ".foo:bar {a: b} + .baz::bang {@extend .foo} + ", + ".foo:bar, .baz:bar::bang {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_goes_lefter_than_not_1, + ".foo::bar {a: b} + .baz:not(.bang) {@extend .foo} + ", + ".foo::bar, .baz:not(.bang)::bar {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_goes_lefter_than_not_2, + "%a { + a:b; + } + b:after:not(:first-child) { + @extend %a; + } + c:s { + @extend %a; + } + d::e { + @extend c; + } + ", + "c:s, d:s::e, b:after:not(:first-child) {\n a: b;\n}\n" +); +grass_test!( + pseudoelement_goes_lefter_than_not_3, + ".foo:not(.bang) {a: b} + .baz::bar {@extend .foo} + ", + ".foo:not(.bang), .baz:not(.bang)::bar {\n a: b;\n}\n" +); +grass_test!( + negation_unification_1, + "%-a :not(.foo).baz {a: b} + :not(.bar) {@extend .baz} -a {@extend %-a} + ", + "-a :not(.foo).baz, -a :not(.foo):not(.bar) {\n a: b;\n}\n" +); +grass_test!( + negation_unification_2, + "%-a :not(.foo).baz {a: b} + :not(.foo) {@extend .baz} -a {@extend %-a} + ", + "-a :not(.foo) {\n a: b;\n}\n" +); +grass_test!( + negation_unification_3, + "%-a :not([a=b]).baz {a: b} + :not([a = b]) {@extend .baz} -a {@extend %-a} + ", + "-a :not([a=b]) {\n a: b;\n}\n" +); +grass_test!( + comma_extendee, + ".foo {a: b} + .bar {c: d} + .baz {@extend .foo, .bar} + ", + ".foo, .baz {\n a: b;\n}\n\n.bar, .baz {\n c: d;\n}\n" +); +grass_test!( + redundant_selector_elimination, + ".foo.bar {a: b} + .x {@extend .foo, .bar} + .y {@extend .foo, .bar} + ", + ".foo.bar, .y, .x {\n a: b;\n}\n" +); +grass_error!( + extend_compound_selector, + "ns|*.foo.bar {a: b} + a.baz {@extend .foo.bar} + ", + "Error: compound selectors may no longer be extended." +); +grass_test!( + compound_extender, + ".foo.bar {a: b} + .baz.bang {@extend .foo} + ", + ".foo.bar, .bar.baz.bang {\n a: b;\n}\n" +); +grass_test!( + compound_extender_unification, + "ns|*.foo.bar {a: b} + a.baz {@extend .foo} + ", + "ns|*.foo.bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender, + ".foo {a: b} + foo bar {@extend .foo} + ", + ".foo, foo bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_unification, + ".foo.bar {a: b} + foo bar {@extend .foo} + ", + ".foo.bar, foo bar.bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_alternates_parents, + ".baz .bip .foo {a: b} + foo .grank bar {@extend .foo} + ", + ".baz .bip .foo, .baz .bip foo .grank bar, foo .grank .baz .bip bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_unifies_identical_parents, + ".baz .bip .foo {a: b} + .baz .bip bar {@extend .foo} + ", + ".baz .bip .foo, .baz .bip bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_unifies_common_substring, + ".baz .bip .bap .bink .foo {a: b} + .brat .bip .bap bar {@extend .foo} + ", + ".baz .bip .bap .bink .foo, .baz .brat .bip .bap .bink bar, .brat .baz .bip .bap .bink bar \ + {\n a: b;\n}\n" +); +grass_test!( + complex_extender_unifies_common_subsequence, + ".a .x .b .y .foo {a: b} + .a .n .b .m bar {@extend .foo} + ", + ".a .x .b .y .foo, .a .x .n .b .y .m bar, .a .n .x .b .y .m bar, .a .x .n .b .m .y bar, .a .n \ + .x .b .m .y bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_chooses_first_subsequence, + ".a .b .c .d .foo {a: b} + .c .d .a .b .bar {@extend .foo} + ", + ".a .b .c .d .foo, .a .b .c .d .a .b .bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_counts_extended_superselectors, + ".a .bip .foo {a: b} + .b .bip.bop .bar {@extend .foo} + ", + ".a .bip .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_child_combinator, + ".baz .foo {a: b} + foo > bar {@extend .foo} + ", + ".baz .foo, .baz foo > bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_child_combinator_1, + "a > b c .c1 {a: b} + a c .c2 {@extend .c1} + ", + "a > b c .c1, a > b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_child_combinator_2, + "a > b c .c1 {a: b} + b c .c2 {@extend .c1} + ", + "a > b c .c1, a > b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_adjacent_sibling_combinator_1, + "a + b c .c1 {a: b} + a c .c2 {@extend .c1} + ", + "a + b c .c1, a + b a c .c2, a a + b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_adjacent_sibling_combinator_2, + "a + b c .c1 {a: b} + a b .c2 {@extend .c1} + ", + "a + b c .c1, a a + b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_adjacent_sibling_combinator_3, + "a + b c .c1 {a: b} + b c .c2 {@extend .c1} + ", + "a + b c .c1, a + b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_sibling_combinator_1, + "a ~ b c .c1 {a: b} + a c .c2 {@extend .c1} + ", + "a ~ b c .c1, a ~ b a c .c2, a a ~ b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_sibling_combinator_2, + "a ~ b c .c1 {a: b} + a b .c2 {@extend .c1} + ", + "a ~ b c .c1, a a ~ b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_finds_common_selectors_around_sibling_combinator_3, + "a ~ b c .c1 {a: b} + b c .c2 {@extend .c1} + ", + "a ~ b c .c1, a ~ b c .c2 {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selectors_doesnt_subsequence_them_1, + ".bip > .bap .foo {a: b} + .grip > .bap .bar {@extend .foo} + ", + ".bip > .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar {\n a: \ + b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selectors_doesnt_subsequence_them_2, + ".bap > .bip .foo {a: b} + .bap > .grip .bar {@extend .foo} + ", + ".bap > .bip .foo, .bap > .bip .bap > .grip .bar, .bap > .grip .bap > .bip .bar {\n a: \ + b;\n}\n" +); +grass_test!( + complex_extender_with_child_selector_unifies_1, + ".baz.foo {a: b} + foo > bar {@extend .foo} + ", + ".baz.foo, foo > bar.baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_child_selector_unifies_2, + ".baz > { + .foo {a: b} + .bar {@extend .foo} + } + ", + ".baz > .foo, .baz > .bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_child_selector_unifies_3, + ".foo { + .bar {a: b} + > .baz {@extend .bar} + } + ", + ".foo .bar, .foo > .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selector_1, + ".foo { + .bar {a: b} + .bip > .baz {@extend .bar} + } + ", + ".foo .bar, .foo .bip > .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selector_2, + ".foo { + .bip .bar {a: b} + > .baz {@extend .bar} + } + ", + ".foo .bip .bar, .foo .bip .foo > .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selector_3, + ".foo > .bar {a: b} + .bip + .baz {@extend .bar} + ", + ".foo > .bar, .foo > .bip + .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selector_4, + ".foo + .bar {a: b} + .bip > .baz {@extend .bar} + ", + ".foo + .bar, .bip > .foo + .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_early_child_selector_5, + ".foo > .bar {a: b} + .bip > .baz {@extend .bar} + ", + ".foo > .bar, .bip.foo > .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_sibling_selector, + ".baz .foo {a: b} + foo + bar {@extend .foo} + ", + ".baz .foo, .baz foo + bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_hacky_selector_1, + ".baz .foo {a: b} + foo + > > + bar {@extend .foo} + ", + ".baz .foo, .baz foo + > > + bar, foo .baz + > > + bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_hacky_selector_2, + ".baz .foo {a: b} + > > bar {@extend .foo} + ", + ".baz .foo, > > .baz bar {\n a: b;\n}\n" +); +grass_test!( + complex_extender_merges_with_the_same_selector, + ".foo { + .bar {a: b} + .baz {@extend .bar} + } + ", + ".foo .bar, .foo .baz {\n a: b;\n}\n" +); +grass_test!( + complex_extender_with_child_selector_merges_with_the_same_selector, + ".foo > .bar .baz {a: b} + .foo > .bar .bang {@extend .baz} + ", + ".foo > .bar .baz, .foo > .bar .bang {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_1, + ".a > + x {a: b} + .b y {@extend x} + ", + ".a > + x, .a .b > + y, .b .a > + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_2, + ".a x {a: b} + .b > + y {@extend x} + ", + ".a x, .a .b > + y, .b .a > + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_3, + ".a > + x {a: b} + .b > + y {@extend x} + ", + ".a > + x, .a .b > + y, .b .a > + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_4, + ".a ~ > + x {a: b} + .b > + y {@extend x} + ", + ".a ~ > + x, .a .b ~ > + y, .b .a ~ > + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_5, + ".a + > x {a: b} + .b > + y {@extend x} + ", + ".a + > x {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_6, + ".a + > x {a: b} + .b > + y {@extend x} + ", + ".a + > x {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_for_hacky_combinators_7, + ".a ~ > + .b > x {a: b} + .c > + .d > y {@extend x} + ", + ".a ~ > + .b > x, .a .c ~ > + .d.b > y, .c .a ~ > + .d.b > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_tilde_1, + ".a.b ~ x {a: b} + .a ~ y {@extend x} + ", + ".a.b ~ x, .a.b ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_tilde_2, + ".a ~ x {a: b} + .a.b ~ y {@extend x} + ", + ".a ~ x, .a.b ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_tilde_3, + ".a ~ x {a: b} + .b ~ y {@extend x} + ", + ".a ~ x, .a ~ .b ~ y, .b ~ .a ~ y, .b.a ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_tilde_4, + "a.a ~ x {a: b} + b.b ~ y {@extend x} + ", + "a.a ~ x, a.a ~ b.b ~ y, b.b ~ a.a ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_1, + ".a.b + x {a: b} + .a ~ y {@extend x} + ", + ".a.b + x, .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_2, + ".a + x {a: b} + .a.b ~ y {@extend x} + ", + ".a + x, .a.b ~ .a + y, .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_3, + ".a + x {a: b} + .b ~ y {@extend x} + ", + ".a + x, .b ~ .a + y, .b.a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_4, + "a.a + x {a: b} + b.b ~ y {@extend x} + ", + "a.a + x, b.b ~ a.a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_5, + ".a.b ~ x {a: b} + .a + y {@extend x} + ", + ".a.b ~ x, .a.b ~ .a + y, .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_6, + ".a ~ x {a: b} + .a.b + y {@extend x} + ", + ".a ~ x, .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_tilde_plus_7, + ".a ~ x {a: b} + .b + y {@extend x} + ", + ".a ~ x, .a ~ .b + y, .b.a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_sibling_1, + ".a > x {a: b} + .b ~ y {@extend x} + ", + ".a > x, .a > .b ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_sibling_2, + ".a > x {a: b} + .b + y {@extend x} + ", + ".a > x, .a > .b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_sibling_3, + ".a ~ x {a: b} + .b > y {@extend x} + ", + ".a ~ x, .b > .a ~ y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_sibling_4, + ".a + x {a: b} + .b > y {@extend x} + ", + ".a + x, .b > .a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_angle_1, + ".a.b > x {a: b} + .b > y {@extend x} + ", + ".a.b > x, .b.a > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_angle_2, + ".a > x {a: b} + .a.b > y {@extend x} + ", + ".a > x, .a.b > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_angle_3, + ".a > x {a: b} + .b > y {@extend x} + ", + ".a > x, .b.a > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_angle_4, + "a.a > x {a: b} + b.b > y {@extend x} + ", + "a.a > x {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_plus_1, + ".a.b + x {a: b} + .b + y {@extend x} + ", + ".a.b + x, .b.a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_plus_2, + ".a + x {a: b} + .a.b + y {@extend x} + ", + ".a + x, .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_plus_3, + ".a + x {a: b} + .b + y {@extend x} + ", + ".a + x, .b.a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_double_plus_4, + "a.a + x {a: b} + b.b + y {@extend x} + ", + "a.a + x {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_1, + ".a.b > x {a: b} + .a y {@extend x} + ", + ".a.b > x, .a.b > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_2, + ".a > x {a: b} + .a.b y {@extend x} + ", + ".a > x, .a.b .a > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_3, + ".a > x {a: b} + .b y {@extend x} + ", + ".a > x, .b .a > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_4, + ".a.b x {a: b} + .a > y {@extend x} + ", + ".a.b x, .a.b .a > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_5, + ".a x {a: b} + .a.b > y {@extend x} + ", + ".a x, .a.b > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_angle_space_6, + ".a x {a: b} + .b > y {@extend x} + ", + ".a x, .a .b > y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_1, + ".a.b + x {a: b} + .a y {@extend x} + ", + ".a.b + x, .a .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_2, + ".a + x {a: b} + .a.b y {@extend x} + ", + ".a + x, .a.b .a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_3, + ".a + x {a: b} + .b y {@extend x} + ", + ".a + x, .b .a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_4, + ".a.b x {a: b} + .a + y {@extend x} + ", + ".a.b x, .a.b .a + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_5, + ".a x {a: b} + .a.b + y {@extend x} + ", + ".a x, .a .a.b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_plus_space_6, + ".a x {a: b} + .b + y {@extend x} + ", + ".a x, .a .b + y {\n a: b;\n}\n" +); +grass_test!( + nested_combinator_unification_1, + ".a > .b + x {a: b} + .c > .d + y {@extend x} + ", + ".a > .b + x, .c.a > .d.b + y {\n a: b;\n}\n" +); +grass_test!( + nested_combinator_unification_2, + ".a > .b + x {a: b} + .c > y {@extend x} + ", + ".a > .b + x, .c.a > .b + y {\n a: b;\n}\n" +); +grass_test!( + combinator_unification_with_newlines, + ".a >\n.b\n+ x {a: b}\n.c\n> .d +\ny {@extend x}\n", + ".a > .b + x, .c.a > .d.b + y {\n a: b;\n}\n" +); +grass_test!( + basic_extend_loop, + ".foo {a: b; @extend .bar} + .bar {c: d; @extend .foo} + ", + ".foo, .bar {\n a: b;\n}\n\n.bar, .foo {\n c: d;\n}\n" +); +grass_test!( + #[ignore = "Rc>"] + three_level_extend_loop, + ".foo {a: b; @extend .bar} + .bar {c: d; @extend .baz} + .baz {e: f; @extend .foo} + ", + ".foo, .baz, .bar {\n a: b;\n}\n\n.bar, .foo, .baz {\n c: d;\n}\n\n.baz, .bar, .foo {\n e: \ + f;\n}\n" +); +grass_test!( + nested_extend_loop, + ".bar { + a: b; + .foo {c: d; @extend .bar} + } + ", + ".bar, .bar .foo {\n a: b;\n}\n.bar .foo {\n c: d;\n}\n" +); +grass_test!( + multiple_extender_merges_with_superset_selector, + ".foo {@extend .bar; @extend .baz} + a.bar.baz {a: b} + ", + "a.bar.baz, a.foo {\n a: b;\n}\n" +); +grass_test!( + inside_control_flow_if, + ".true { color: green; } + .false { color: red; } + .also-true { + @if true { @extend .true; } + @else { @extend .false; } + } + .also-false { + @if false { @extend .true; } + @else { @extend .false; } + } + ", + ".true, .also-true {\n color: green;\n}\n\n.false, .also-false {\n color: red;\n}\n" +); +grass_test!( + inside_control_flow_for, + " + .base-0 { color: green; } + .base-1 { display: block; } + .base-2 { border: 1px solid blue; } + .added { + @for $i from 0 to 3 { + @extend .base-#{$i}; + } + } + ", + ".base-0, .added {\n color: green;\n}\n\n.base-1, .added {\n display: block;\n}\n\n.base-2, \ + .added {\n border: 1px solid blue;\n}\n" +); +grass_test!( + inside_control_flow_while, + " + .base-0 { color: green; } + .base-1 { display: block; } + .base-2 { border: 1px solid blue; } + .added { + $i : 0; + @while $i < 3 { + @extend .base-#{$i}; + $i : $i + 1; + } + } + ", + ".base-0, .added {\n color: green;\n}\n\n.base-1, .added {\n display: block;\n}\n\n.base-2, \ + .added {\n border: 1px solid blue;\n}\n" +); +grass_test!( + basic_placeholder, + "%foo {a: b} + .bar {@extend %foo} + ", + ".bar {\n a: b;\n}\n" +); +grass_test!( + unused_placeholder, + "%foo {a: b} + %bar {a: b} + .baz {@extend %foo} + ", + ".baz {\n a: b;\n}\n" +); +grass_test!( + placeholder_descendant, + "#context %foo a {a: b} + .bar {@extend %foo} + ", + "#context .bar a {\n a: b;\n}\n" +); +grass_test!( + semi_placeholder, + "#context %foo, .bar .baz {a: b} + + .bat { + @extend %foo; + } + ", + "#context .bat, .bar .baz {\n a: b;\n}\n" +); +grass_test!( + placeholder_with_multiple_extenders, + "%foo {a: b} + .bar {@extend %foo} + .baz {@extend %foo} + ", + ".baz, .bar {\n a: b;\n}\n" +); +grass_test!( + placeholder_interpolation, + "$foo: foo; + + %#{$foo} {a: b} + .bar {@extend %foo} + ", + ".bar {\n a: b;\n}\n" +); +grass_test!( + media_inside_placeholder, + "%foo {bar {@media screen {a {b: c}}}} + .baz {c: d} + ", + ".baz {\n c: d;\n}\n" +); +grass_test!( + extend_within_media, + "@media screen { + .foo {a: b} + .bar {@extend .foo} + } + ", + "@media screen {\n .foo, .bar {\n a: b;\n }\n}\n" +); +grass_test!( + extend_within_unknown_at_rule, + "@unknown { + .foo {a: b} + .bar {@extend .foo} + } + ", + "@unknown {\n .foo, .bar {\n a: b;\n }\n}\n" +); +grass_test!( + extend_within_nested_at_rules, + "@media screen { + @unknown { + .foo {a: b} + .bar {@extend .foo} + } + } + ", + "@media screen {\n @unknown {\n .foo, .bar {\n a: b;\n }\n }\n}\n" +); +grass_test!( + #[ignore = "media queries are not yet parsed correctly"] + extend_within_separate_media_queries, + "@media screen {.foo {a: b}} + @media screen {.bar {@extend .foo}} + ", + "@media screen {\n .foo, .bar {\n a: b;\n }\n}\n" +); +grass_test!( + #[ignore = "media queries are not yet parsed correctly"] + extend_within_separate_unknown_at_rules, + "@unknown {.foo {a: b}} + @unknown {.bar {@extend .foo}} + ", + "@unknown {\n .foo, .bar {\n a: b;\n }\n}\n@unknown {}\n" +); +grass_test!( + #[ignore = "media queries are not yet parsed correctly"] + extend_within_separate_nested_at_rules, + "@media screen {@flooblehoof {.foo {a: b}}} + @media screen {@flooblehoof {.bar {@extend .foo}}} + ", + "@media screen {\n @flooblehoof {\n .foo, .bar {\n a: b;\n }\n }\n}\n@media \ + screen {\n @flooblehoof {}\n}\n" +); +grass_test!( + extend_succeeds_when_one_extend_fails_but_others_dont, + "a.bar {a: b} + .bar {c: d} + b.foo {@extend .bar} + ", + "a.bar {\n a: b;\n}\n\n.bar, b.foo {\n c: d;\n}\n" +); +grass_test!( + optional_extend_succeeds_when_extendee_doesnt_exist, + ".foo {@extend .bar !optional}", + "" +); +grass_test!( + optional_extend_succeeds_when_extension_fails, + "a.bar {a: b} + b.foo {@extend .bar !optional} + ", + "a.bar {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_1, + "%x#bar {a: b} // Add an id to make the results have high specificity + %y, %y::fblthp {@extend %x} + a {@extend %y} + ", + "a#bar, a#bar::fblthp {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_2, + "%x#bar {a: b} + %y, %y:fblthp {@extend %x} + a {@extend %y} + ", + "a#bar {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_3, + "%x#bar {a: b} + %y, %y:first-line {@extend %x} + a {@extend %y} + ", + "a#bar, a#bar:first-line {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_4, + "%x#bar {a: b} + %y, %y:first-letter {@extend %x} + a {@extend %y} + ", + "a#bar, a#bar:first-letter {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_5, + "%x#bar {a: b} + %y, %y:before {@extend %x} + a {@extend %y} + ", + "a#bar, a#bar:before {\n a: b;\n}\n" +); +grass_test!( + psuedo_element_superselector_6, + "%x#bar {a: b} + %y, %y:after {@extend %x} + a {@extend %y} + ", + "a#bar, a#bar:after {\n a: b;\n}\n" +); +grass_test!( + multiple_source_redundancy_elimination, + "%default-color {color: red} + %alt-color {color: green} + + %default-style { + @extend %default-color; + &:hover {@extend %alt-color} + &:active {@extend %default-color} + } + + .test-case {@extend %default-style} + ", + ".test-case:active, .test-case {\n color: red;\n}\n\n.test-case:hover {\n color: green;\n}\n" +); +grass_test!( + nested_sibling_extend, + ".foo {@extend .bar} + + .parent { + .bar { + a: b; + } + .foo { + @extend .bar + } + } + ", + ".parent .bar, .parent .foo {\n a: b;\n}\n" +); +grass_test!( + parent_and_sibling_extend, + "%foo %bar%baz {a: b} + + .parent1 { + @extend %foo; + .child1 {@extend %bar} + } + + .parent2 { + @extend %foo; + .child2 {@extend %baz} + } + ", + ".parent1 .parent2 .child1.child2, .parent2 .parent1 .child1.child2 {\n a: b;\n}\n" +); +grass_test!( + nested_extend_specificity, + "%foo {a: b} + + a { + :b {@extend %foo} + :b:c {@extend %foo} + } + ", + "a :b:c, a :b {\n a: b;\n}\n" +); +grass_test!( + double_extend_optimization, + "%foo %bar { + a: b; + } + + .parent1 { + @extend %foo; + + .child { + @extend %bar; + } + } + + .parent2 { + @extend %foo; + } + ", + ".parent1 .child {\n a: b;\n}\n" +); +grass_test!( + #[ignore = "media queries are not yet parsed correctly"] + extend_inside_double_nested_media, + "@media all { + @media (orientation: landscape) { + %foo {color: blue} + .bar {@extend %foo} + } + } + ", + "@media (orientation: landscape) {\n .bar {\n color: blue;\n }\n}\n" +); +grass_test!( + partially_failed_extend, + "test { @extend .rc; } + .rc {color: white;} + .prices span.pill span.rc {color: red;} + ", + ".rc, test {\n color: white;\n}\n\n.prices span.pill span.rc {\n color: red;\n}\n" +); +grass_test!( + newline_near_combinator, + ".a + + .b x {a: b} + .c y {@extend x} + ", + ".a + .b x, .a + .b .c y, .c .a + .b y {\n a: b;\n}\n" +); +grass_test!( + duplicated_selector_with_newlines, + ".example-1-1, + .example-1-2, + .example-1-3 { + a: b; + } + + .my-page-1 .my-module-1-1 {@extend .example-1-2} + ", + ".example-1-1,\n.example-1-2,\n.my-page-1 .my-module-1-1,\n.example-1-3 {\n a: b;\n}\n" +); +grass_test!( + nested_selector_with_child_selector_hack_extendee, + "> .foo {a: b} + foo bar {@extend .foo} + ", + "> .foo, > foo bar {\n a: b;\n}\n" +); +grass_test!( + nested_selector_with_child_selector_hack_extender, + ".foo .bar {a: b} + > foo bar {@extend .bar} + ", + ".foo .bar, > .foo foo bar, > foo .foo bar {\n a: b;\n}\n" +); +grass_test!( + nested_selector_with_child_selector_hack_extender_and_extendee, + "> .foo {a: b} + > foo bar {@extend .foo} + ", + "> .foo, > foo bar {\n a: b;\n}\n" +); +grass_test!( + nested_selector_with_child_selector_hack_extender_and_sibling_extendee, + "~ .foo {a: b} + > foo bar {@extend .foo} + ", + "~ .foo {\n a: b;\n}\n" +); +grass_test!( + nested_selector_with_child_selector_hack_extender_and_extendee_newline, + "> .foo {a: b}\nflip,\n> foo bar {@extend .foo}\n", + "> .foo, > flip,\n> foo bar {\n a: b;\n}\n" +); +grass_test!( + extended_parent_and_child_redundancy_elimination, + "a { + b {a: b} + c {@extend b} + } + d {@extend a} + ", + "a b, d b, a c, d c {\n a: b;\n}\n" +); +grass_test!( + redundancy_elimination_when_it_would_reduce_specificity, + "a {a: b} + a.foo {@extend a} + ", + "a, a.foo {\n a: b;\n}\n" +); +grass_test!( + redundancy_elimination_when_it_would_preserve_specificity, + ".bar a {a: b} + a.foo {@extend a} + ", + ".bar a {\n a: b;\n}\n" +); +grass_test!( + redundancy_elimination_never_eliminates_base_selector, + "a.foo {a: b} + .foo {@extend a} + ", + "a.foo, .foo {\n a: b;\n}\n" +); +grass_test!( + cross_branch_redundancy_elimination_1, + "%x .c %y {a: b} + .a, .b {@extend %x} + .a .d {@extend %y} + ", + ".a .c .d, .b .c .a .d {\n a: b;\n}\n" +); +grass_test!( + cross_branch_redundancy_elimination_2, + ".e %z {a: b} + %x .c %y {@extend %z} + .a, .b {@extend %x} + .a .d {@extend %y} + ", + ".e .a .c .d, .e .b .c .a .d, .a .e .b .c .d, .a .c .e .d, .b .c .e .a .d {\n a: b;\n}\n" +); +grass_test!( + extend_with_universal_selector, + "%-a *.foo1 {a: b} + a {@extend .foo1} + -a {@extend %-a} + + %-b *|*.foo2 {b: b} + b {@extend .foo2} + -b {@extend %-b} + ", + "-a *.foo1, -a a {\n a: b;\n}\n\n-b *|*.foo2, -b b {\n b: b;\n}\n" +); +grass_test!( + extend_with_universal_selector_empty_namespace, + "%-a |*.foo {a: b} + a {@extend .foo} + -a {@extend %-a} + ", + "-a |*.foo {\n a: b;\n}\n" +); +grass_test!( + extend_with_universal_selector_different_namespace, + "%-a ns|*.foo {a: b} + a {@extend .foo} + -a {@extend %-a} + ", + "-a ns|*.foo {\n a: b;\n}\n" +); +grass_test!( + unify_root_pseudo_element, + "// We assume that by default classes don't apply to the :root unless marked explicitly. + :root .foo-1 { test: 1; } + .bar-1 .baz-1 { @extend .foo-1; } + + // We know the two classes must be the same :root element so we can combine them. + .foo-2:root .bar-2 { test: 2; } + .baz-2:root .bang-2 { @extend .bar-2; } + + // This extend should not apply because the :root elements are different. + html:root .bar-3 { test: 3; } + xml:root .bang-3 { @extend .bar-3} + + // We assume that direct descendant of the :root is not the same element as a descendant. + .foo-4:root > .bar-4 .x-4 { test: 4; } + .baz-4:root .bang-4 .y-4 {@extend .x-4} + ", + ":root .foo-1, :root .bar-1 .baz-1 {\n test: 1;\n}\n\n.foo-2:root .bar-2, .baz-2.foo-2:root \ + .bang-2 {\n test: 2;\n}\n\nhtml:root .bar-3 {\n test: 3;\n}\n\n.foo-4:root > .bar-4 .x-4, \ + .baz-4.foo-4:root > .bar-4 .bang-4 .y-4 {\n test: 4;\n}\n" +); +grass_test!( + compound_unification_in_not, + "// Make sure compound selectors are unified when two :not()s are extended. + // :not() is special here because it's the only selector that's extended by + // adding to the compound selector, rather than creating a new selector list. + .a {@extend .c} + .b {@extend .d} + :not(.c):not(.d) {a: b} + ", + ":not(.c):not(.a):not(.d):not(.b) {\n a: b;\n}\n" +); +grass_test!( + #[ignore = "media queries are not yet parsed correctly"] + does_not_move_page_block_in_media, + "@media screen { + a { x:y; } + @page {} + } + ", + "@media screen {\n a {\n x: y;\n }\n\n @page {}\n}\n" +); +grass_test!( + escaped_selector, + "// Escapes in selectors' identifiers should be normalized before `@extend` is + // applied. + .foo {escape: none} + \\.foo {escape: slash dot} + \\2E foo {escape: hex} + + .bar {@extend \\02e foo} + ", + ".foo {\n escape: none;\n}\n\n\\.foo, .bar {\n escape: slash dot;\n}\n\n\\.foo, .bar {\n \ + escape: hex;\n}\n" +); +grass_test!( + #[ignore = "Rc>"] + extend_extender, + "// For implementations like Dart Sass that process extensions as they occur, + // extending rules that contain their own extends needs special handling. + .b {@extend .a} + .c {@extend .b} + .a {x: y} + ", + ".a, .b, .c {\n x: y;\n}\n" +); +grass_test!( + extend_result_of_extend, + "// The result of :not(.c) being extended should itself be extendable. + .a {@extend :not(.b)} + .b {@extend .c} + :not(.c) {x: y} + ", + ":not(.c):not(.b), .a:not(.c) {\n x: y;\n}\n" +); +grass_test!( + extend_self, + "// This shouldn't change the selector. + .c, .a .b .c, .a .c .b {x: y; @extend .c} + ", + ".c, .a .b .c, .a .c .b {\n x: y;\n}\n" +); +grass_test!( + dart_sass_issue_146, + "%btn-style-default { + background: green; + &:hover{ + background: black; + } + } + + button { + @extend %btn-style-default; + } + ", + "button {\n background: green;\n}\nbutton:hover {\n background: black;\n}\n" +); +grass_test!( + nested_compound_unification, + "// Make sure compound unification properly handles weaving together parent + // selectors. + .a .b {@extend .e} + .c .d {@extend .f} + .e.f {x: y} + ", + ".e.f, .a .f.b, .c .e.d, .a .c .b.d, .c .a .b.d {\n x: y;\n}\n" +); +grass_test!( + not_into_not_not, + "// Regression test for dart-sass#191. + :not(:not(.x)) {a: b} + :not(.y) {@extend .x} + ", + ":not(:not(.x)) {\n a: b;\n}\n" +); +grass_test!( + selector_list, + ".foo {a: b} + .bar {x: y} + + // Extending a selector list is equivalent to writing two @extends. + .baz {@extend .foo, .bar} + + // The selector list should be parsed after interpolation is resolved. + .bang {@extend .foo #{\",\"} .bar} + ", + ".foo, .bang, .baz {\n a: b;\n}\n\n.bar, .bang, .baz {\n x: y;\n}\n" +); +grass_test!( + selector_list_after_selector, + "a { + color: red; + } + + b, + c { + @extend a; + }", + "a, b,\nc {\n color: red;\n}\n" +); +grass_test!( + selector_list_before_selector, + "b, c { + @extend a; + } + + a { + color: red; + }", + "a, b, c {\n color: red;\n}\n" +); +grass_test!( + selector_list_of_selector_pseudo_classes_after_selector, + "foo { + color: black; + } + + a:current(foo), + :current(foo) { + @extend foo; + }", + "foo, a:current(foo),\n:current(foo) {\n color: black;\n}\n" +); +grass_test!( + extend_pseudo_selector_class_containing_combinator_without_rhs_selector, + ":has(a >) b { + @extend b; + color: red; + }", + ":has(a >) b, :has(a >) :has(a >) :has(a >) b, :has(a >) :has(a >) :has(a >) b {\n color: \ + red;\n}\n" +); +grass_error!( + extend_optional_keyword_not_complete, + "a { + @extend a !opt; + }", + "Error: Expected \"optional\"." +); +grass_error!( + extend_contains_parent_in_compound_selector, + "a { + @extend &b:c; + }", + "Error: Parent selectors aren't allowed here." +); + +// todo: extend_loop (massive test) +// todo: extend tests in folders diff --git a/css/parser/tests/grass_for.rs b/css/parser/tests/grass_for.rs new file mode 100644 index 000000000000..56c75a19814f --- /dev/null +++ b/css/parser/tests/grass_for.rs @@ -0,0 +1,183 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + for_1_through_3, + "@for $i from 1 through 3 {\n a {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n}\n\na {\n color: 2;\n}\n\na {\n color: 3;\n}\n" +); +grass_test!( + for_1_to_3, + "@for $i from 1 to 3 {\n a {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n}\n\na {\n color: 2;\n}\n" +); +grass_test!( + for_3_through_1, + "@for $i from 3 through 1 {\n a {\n color: $i;\n }\n}\n", + "a {\n color: 3;\n}\n\na {\n color: 2;\n}\n\na {\n color: 1;\n}\n" +); +grass_test!( + for_3_to_1, + "@for $i from 3 to 1 {\n a {\n color: $i;\n }\n}\n", + "a {\n color: 3;\n}\n\na {\n color: 2;\n}\n" +); +grass_test!( + for_var_through_var, + "$a: 1;\n$b: 3;\n@for $x from $a through $b {\n div {\n color: $x;\n }\n}\n", + "div {\n color: 1;\n}\n\ndiv {\n color: 2;\n}\n\ndiv {\n color: 3;\n}\n" +); +grass_test!( + for_var_decl, + "@for $x from 1 to 3 {\n $limit: $x;\n\n a {\n color: $limit;\n }\n}\n", + "a {\n color: 1;\n}\n\na {\n color: 2;\n}\n" +); +grass_test!( + for_styles, + "a {\n @for $i from 1 to 3 {\n color: $i;\n }\n}\n", + "a {\n color: 1;\n color: 2;\n}\n" +); +grass_test!( + scope, + "a {\n $a: red;\n @for $i from 1 to 3 {\n $a: blue;\n }\n color: $a;\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + simple_return_in_function, + "@function foo() {\n @for $i from 1 to 2 {\n @return $i;\n }\n}\na {\n color: \ + foo();\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + return_gated_by_if_in_function, + "@function foo() {\n @for $i from 1 through 2 {\n @if $i==2 {\n @return $i;\n }\n }\n}\na {\n color: foo();\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + inner_if, + "a {\n @for $i from 1 to 3 {\n @if $i==2 {\n color: red;\n }\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + from_negative_to_positive, + "@for $i from -1 to 3 {\n a {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n\na {\n color: red;\n}\n\na {\n color: red;\n}\n\na {\n color: \ + red;\n}\n" +); +grass_test!( + from_negative_to_negative, + "@for $i from -1 to -3 {\n a {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n\na {\n color: red;\n}\n" +); +grass_test!( + variable_named_to_as_value, + "$to: 0; + + @for $i from $to to 1 { + a { + color: red; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + to_crazy_interpolation, + "a { + @for $i from 0 to length(#{\"#{\"\\\\}}}{{{\"}#\"}) { + color: #{\"#{\"\\\\}}}{{{\"}#\"}; + } + }", + "a {\n color: \\}}}{{{#;\n}\n" +); +grass_test!( + from_crazy_interpolation, + "a { + @for $i from length(#{\"#{\"\\\\}}}{{{\"}#\"}) to 2 { + color: #{\"#{\"\\\\}}}{{{\"}#\"}; + } + }", + "a {\n color: \\}}}{{{#;\n}\n" +); +grass_test!( + indexing_variable_does_not_affect_outer_scopes, + "a { + $a: 1; + $b: 1; + + @for $a from 1 through 2 { + color: $a; + $b: $a; + } + + color: $a; + color: $b; + }", + "a {\n color: 1;\n color: 2;\n color: 1;\n color: 2;\n}\n" +); +grass_test!( + multiline_comments_everywhere, + " /**/ @for /**/ $i /**/ from /**/ 0 /**/ to /**/ 2 /**/ {} /**/ ", + "/**/\n/**/\n" +); +grass_test!( + uppercase_keywords, + "@for $i FROM 0 TO 2 { + @foo; + }", + "@foo;\n@foo;\n" +); +grass_error!( + missing_keyword_from, + "@for $i fro", + "Error: Expected \"from\"." +); +grass_error!(missing_dollar_sign, "@for", "Error: expected \"$\"."); +grass_error!( + variable_missing_identifier, + "@for $", + "Error: Expected identifier." +); +grass_error!( + from_value_is_decimal, + "@for $i from 0.5 to 2 {}", + "Error: 0.5 is not an int." +); +grass_error!( + to_value_is_decimal, + "@for $i from 0 to 1.5 {}", + "Error: 1.5 is not an int." +); +grass_error!( + from_value_is_non_numeric, + "@for $i from red to 2 {}", + "Error: red is not a number." +); +grass_error!( + to_value_is_non_numeric, + "@for $i from 0 to red {}", + "Error: red is not a number." +); +grass_error!( + through_i32_max, + "@for $i from 0 through 2147483647 {}", + "Error: 2147483647 is not an int." +); +grass_error!( + from_i32_max, + "@for $i from 2147483647 through 0 {}", + "Error: 2147483647 is not an int." +); +grass_error!( + from_nan, + "@for $i from (0/0) through 0 {}", + "Error: NaN is not an int." +); +grass_error!( + to_nan, + "@for $i from 0 through (0/0) {}", + "Error: NaN is not an int." +); +grass_error!( + to_and_from_i32_min, + "@for $i from -2147483648 through -2147483648 {}", + "Error: -2147483648 is not an int." +); diff --git a/css/parser/tests/grass_functions.rs b/css/parser/tests/grass_functions.rs new file mode 100644 index 000000000000..cbce68afc220 --- /dev/null +++ b/css/parser/tests/grass_functions.rs @@ -0,0 +1,348 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + return_num, + "@function a() {\n @return 1;\n}\n\nb {\ncolor: a();\n}\n", + "b {\n color: 1;\n}\n" +); +grass_test!( + return_spaced_list, + "@function a() {\n @return a b;\n}\n\nb {\ncolor: a();\n}\n", + "b {\n color: a b;\n}\n" +); +grass_test!( + single_arg, + "@function a($c) {\n @return $c;\n}\n\nb {\ncolor: a(1);\n}\n", + "b {\n color: 1;\n}\n" +); +grass_test!( + return_variable, + "@function a($a) {\n @return $a;\n}\n\nb {\ncolor: a(1);\n}\n", + "b {\n color: 1;\n}\n" +); +grass_test!( + function_call_as_arg, + "@function a($a) {\n @return $a;\n}\n\nb {\ncolor: a(a(2));\n}\n", + "b {\n color: 2;\n}\n" +); +grass_test!( + function_named_arg_value_variable, + "$x: red;\n\n@function a($a) {\n @return $a;\n}\n\nb {\ncolor: a($a: $x);\n}\n", + "b {\n color: red;\n}\n" +); +grass_test!( + function_trailing_comma, + "@function a($a) {\n @return $a;\n}\n\nb {\ncolor: a(red,);\n}\n", + "b {\n color: red;\n}\n" +); +grass_test!( + return_no_semicolon, + "@function a() {\n @return 1\n}\n\nb {\ncolor: a();\n}\n", + "b {\n color: 1;\n}\n" +); +grass_test!( + two_returns, + "@function a() {\n @return 1; @return 2;\n}\n\nb {\ncolor: a();\n}\n", + "b {\n color: 1;\n}\n" +); +grass_test!( + value_after_variable, + "$x: 0;\na {\n color: if($x != 0, a, b);\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + function_decl_in_ruleset, + "a {\n @function foo() {\n @return 3;\n }\n color: foo();\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + function_decl_in_foreign_ruleset, + "a {\n @function foo() {\n @return 3;\n }\n}\nb {\n color: foo();\n}\n", + "b {\n color: foo();\n}\n" +); +grass_test!( + global_function_in_scope, + "@function f() {\n @return g();\n}\n@function g() {\n @return false;\n}\na {\n color: \ + f();\n color: g();\n}\n", + "a {\n color: false;\n color: false;\n}\n" +); +grass_test!( + square_bracket_comma_separated, + "@function foo($a) {\n @return $a;\n}\n\na {\n color: foo([a, b]);\n}\n", + "a {\n color: [a, b];\n}\n" +); +grass_test!( + eats_quoted_content, + "a {\n color: unquote(\"a, b, c, d\");\n}\n", + "a {\n color: a, b, c, d;\n}\n" +); +grass_test!( + variable_declaration, + "@function str-replace($string, $search, $replace: \"\") {\n $index: $string;\n @return \ + $index;\n}\n\na {\n color: str-replace(\"a#b#c\", \"#\", \":\");\n}", + "a {\n color: \"a#b#c\";\n}\n" +); +grass_error!( + missing_name, + "@function() {}", + "Error: Expected identifier." +); +grass_error!( + args_do_not_start_with_var, + "@function foo(FOO) {}", + "Error: expected \")\"." +); +grass_error!( + double_comma_args, + "@function foo($a,$b,,) {}", + "Error: expected \")\"." +); +grass_error!( + body_missing_closing_curly_brace, + "@function foo() {", + "Error: expected \"}\"." +); +grass_test!( + does_not_modify_local_variables, + "@function bar($color-name) { + @if $color-name==bar { + @error bar; + } + + $color: bar; + @return null; + } + + @function foo($a, $b) { + @return \"success!\"; + } + + a { + $color: foo; + + color: foo(bar($color), bar($color)); + }", + "a {\n color: \"success!\";\n}\n" +); +grass_error!( + denies_function_declaration_in_control_flow, + "@if true {\n @function foo() {}\n}\n", + "Error: Functions may not be declared in control directives." +); +grass_error!( + denies_function_declaration_with_reserved_name, + "@function url() {}", + "Error: Invalid function name." +); +grass_test!( + function_finds_outer_local_variable, + "a { + $a: red; + + @function foo() { + @return $a; + } + + color: foo(); + }", + "a {\n color: red;\n}\n" +); +grass_test!( + function_ignores_the_scope_with_which_it_was_defined, + "a { + $a: red; + @function foo() { + @return $a; + } + $a: green; + color: foo(); + }", + "a {\n color: green;\n}\n" +); +grass_test!( + function_defined_and_called_at_toplevel_can_recognize_inner_variables, + "@function foo($level) { + $level: abs($level); + + @return $level; + } + + @mixin bar($a) { + a { + color: $a; + } + } + + @include bar(foo(-9));", + "a {\n color: 9;\n}\n" +); +grass_test!( + redeclaration_in_inner_scope, + "@function foo() { + @return foo; + } + + a { + color: foo(); + + @function foo() { + @return bar; + } + + a { + @function foo() { + @return baz; + } + } + + color: foo(); + }", + "a {\n color: foo;\n color: bar;\n}\n" +); +grass_error!( + disallows_unknown_at_rule, + "@function foo() { + @foo; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_media_query, + "@function foo() { + @media screen {}; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_at_root, + "@function foo() { + @at-root {}; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_charset, + "@function foo() { + @charset 'utf-8'; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_extend, + "@function foo() { + @extend a; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_keyframes, + "@function foo() { + @keyframes foo {} + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_supports, + "@function foo() { + @supports foo {} + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_import, + "@function foo() { + @import \"foo.css\"; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_inner_function_declaration, + "@function foo() { + @function bar() {} + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_include, + "@function foo() { + @include bar; + } + + a { + color: foo(); + }", + "Error: This at-rule is not allowed here." +); +grass_error!( + disallows_selectors, + "@function foo($a) { + functiona { + @return $a; + } + } + + a { + color: foo(nul); + }", + "Error: Functions can only contain variable declarations and control directives." +); +grass_test!( + allows_multiline_comment, + "@function foo($a) { + /* foo */ + @return $a; + } + + a { + color: foo(nul); + }", + "a {\n color: nul;\n}\n" +); +grass_test!( + allows_multiline_comment_between_args, + "@function foo /**/ () /**/ { + @return red; + } + + a { + color: foo(); + }", + "a {\n color: red;\n}\n" +); diff --git a/css/parser/tests/grass_get_function.rs b/css/parser/tests/grass_get_function.rs new file mode 100644 index 000000000000..8a0aec1477c3 --- /dev/null +++ b/css/parser/tests/grass_get_function.rs @@ -0,0 +1,128 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + different_function_same_body_not_equal, + "@function user-defined() {@return null} + $first-reference: get-function(user-defined); + @function user-defined() {@return null} + $second-reference: get-function(user-defined); + a {b: $first-reference == $second-reference}", + "a {\n b: false;\n}\n" +); +grass_test!( + same_function_equal, + "@function user-defined() {@return null} + a {b: get-function(user-defined) == get-function(user-defined)}", + "a {\n b: true;\n}\n" +); +grass_test!( + different_name_same_body_not_equal, + "@function user-defined-1() {@return null} + @function user-defined-2() {@return null} + a {b: get-function(user-defined-1) == get-function(user-defined-2)}", + "a {\n b: false;\n}\n" +); +grass_test!( + type_of_user_defined_function, + "@function user-defined() {@return null} + a {b: type-of(get-function(user-defined));}", + "a {\n b: function;\n}\n" +); +grass_test!( + type_of_builtin_function, + "a {b: type-of(get-function(lighten));}", + "a {\n b: function;\n}\n" +); +grass_test!( + same_builtin_function_is_equal, + "a {b: get-function(lighten) == get-function(lighten);}", + "a {\n b: true;\n}\n" +); +grass_test!( + different_builtin_function_not_equal, + "a {b: get-function(lighten) == get-function(darken);}", + "a {\n b: false;\n}\n" +); +grass_test!( + builtin_not_equal_to_user_defined, + "@function user-defined() {\n @return foo;\n}\n + a {b: get-function(lighten) == get-function(user-defined);}", + "a {\n b: false;\n}\n" +); +grass_test!( + inspect_builtin_function, + "a {b: inspect(get-function(lighten));}", + "a {\n b: get-function(\"lighten\");\n}\n" +); +grass_test!( + call_user_defined_no_args, + "@function user-defined() {\n @return foo;\n}\n + a {b: call(get-function(user-defined));}", + "a {\n b: foo;\n}\n" +); +grass_test!( + call_user_defined_positional_args, + "@function user-defined($a, $b) {\n @return $a, $b;\n}\n + a {b: call(get-function(user-defined), a, b);}", + "a {\n b: a, b;\n}\n" +); +grass_test!( + call_user_defined_keyword_args, + "@function user-defined($a, $b) {\n @return $a, $b;\n}\n + a {b: call(get-function(user-defined), $a: a, $b: b);}", + "a {\n b: a, b;\n}\n" +); +grass_test!( + call_builtin_positional_args, + "a {b: call(get-function(lighten), red, 5);}", + "a {\n b: #ff1a1a;\n}\n" +); +grass_test!( + call_builtin_keyword_args, + "a {b: call(get-function(lighten), $color: red, $amount: 5);}", + "a {\n b: #ff1a1a;\n}\n" +); +grass_test!( + call_user_defined_super_selector, + "@function user-defined() {\n @return &;\n}\n + a {b: call(get-function(user-defined));}", + "a {\n b: a;\n}\n" +); +grass_error!( + undefined_function, + "a {color: get-function(foo);}", + "Error: Function not found: foo" +); +grass_error!( + non_function_call, + "a {color: call(4);}", + "Error: $function: 4 is not a function reference." +); +grass_error!( + emit_plain_get_function_is_invalid_css, + "a {color: get-function(lighten);}", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_test!( + hyphen_underscore_normalized, + "@function _test() { + @return 'hello'; + } + + a { + color: inspect(get-function('_test')); + color: inspect(get-function('-test')); + }", + "a {\n color: get-function(\"-test\");\n color: get-function(\"-test\");\n}\n" +); +grass_test!( + nested_call_and_get_function, + "a {\n color: call(call(get-function(get-function), darken), red, 10%);\n}\n", + "a {\n color: #cc0000;\n}\n" +); +grass_test!( + get_function_of_module, + "@use 'sass:math';\na {\n color: call(get-function(cos, $module: math), 2);\n}\n", + "a {\n color: -0.4161468365;\n}\n" +); diff --git a/css/parser/tests/grass_if.rs b/css/parser/tests/grass_if.rs new file mode 100644 index 000000000000..00414bc59dfa --- /dev/null +++ b/css/parser/tests/grass_if.rs @@ -0,0 +1,213 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + if_toplevel_true, + "@if true {\n a {\n color: foo;\n}\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + if_inner_true, + "a {\n @if true {\n color: foo;\n}\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + if_toplevel_false, + "@if false {\n a {\n color: foo;\n}\n}\n", + "" +); +grass_test!( + if_inner_false, + "a {\n @if false {\n color: foo;\n}\n}\n", + "" +); +grass_test!( + if_else_toplevel_true, + "@if true {\n a {\n color: foo;\n}\n} @else {\n b {\n color: bar;\n}\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + if_else_inner_true, + "a {\n @if true {\n color: foo;\n} @else {\n color: bar;\n}\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + if_else_toplevel_false, + "@if false {\n a {\n color: foo;\n}\n} @else {\n a {\n color: bar;\n}\n}\n", + "a {\n color: bar;\n}\n" +); +grass_test!( + if_else_inner_false, + "a {\n @if false {\n color: foo;\n} @else {\n color: bar;\n}\n}\n", + "a {\n color: bar;\n}\n" +); +grass_error!( + no_brace_after_else, + "@if false {} @else -}", + "Error: expected \"{\"." +); +grass_test!( + if_else_if_no_else, + "a {\n @if false {\n color: red;\n} @else if true {\n color: blue;\n}\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + if_false_else_if_false_else, + "a {\n @if false {\n color: red;\n} @else if false {\n color: blue;\n} @else {\n \ + color: green;\n}\n}\n", + "a {\n color: green;\n}\n" +); +grass_test!( + if_false_else_if_true_else, + "a {\n @if false {\n color: red;\n} @else if true {\n color: blue;\n} @else {\n \ + color: green;\n}\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + if_inner_style_missing_semicolon, + "a {\n @if true {\n color: red\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + atrule_other_than_else_immediately_following, + "a {\n @if true {\n b {\n background: gray;\n }\n }\n\n @if true {\n b {\n background: gray;\n }\n }\n}\n", + "a b {\n background: gray;\n}\na b {\n background: gray;\n}\n" +); +grass_test!( + nested_if_in_function, + "@function foo($value) {\n @if true {\n @if false {\n @error foo;\n }\n\n \ + @else {\n @return $value;\n }\n }\n} + a { color: foo(bar); }", + "a {\n color: bar;\n}\n" +); +grass_test!( + multiline_comments_surrounding_condition_empty, + "@if/**/true/**/{ a { color: red; } }", + "a {\n color: red;\n}\n" +); +grass_test!( + multiline_comments_surrounding_condition, + "@if/* pre 1 */true/* post 1 */{ a { color: red; } }", + "a {\n color: red;\n}\n" +); +grass_test!( + escaped_if, + "@\\69 f true {\n a {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + uppercase_escaped_if, + "@\\49 f true {\n a {\n color: red;\n }\n}\n", + "@If true {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + escaped_else, + "@if false {}\n\n@\\65lse {\n a {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + uppercase_escaped_else, + "@if false {}\n\n@\\45lse {\n a {\n color: red;\n }\n}\n", + "@Else {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + uppercase_else, + "@if false {}\n\n@Else {\n a {\n color: red;\n }\n}\n", + "@Else {\n a {\n color: red;\n }\n}\n" +); +grass_test!(uppercase_if, "@If true {\n a {\n color: red;\n }\n}\n"); +grass_error!(nothing_after_if, "@if", "Error: Expected expression."); +grass_error!( + nothing_after_dollar, + "@if ${}", + "Error: Expected identifier." +); +grass_error!(no_condition, "@if{}", "Error: Expected expression."); +grass_error!( + nothing_after_open_curly, + "@if foo {", + "Error: expected \"}\"." +); +grass_error!( + first_condition_error, + "@if 1 + 1 =s {\n}", + "Error: expected \"=\"." +); +grass_test!( + conditions_evaluated_lazily, + "$p: null; + @if $p==null {} + @else if not comparable($p, 0) {}", + "" +); +grass_test!( + at_rule_inside_ruleset, + "@mixin foo {\n color: red;\n}\n\n@if true {\n a {\n @include foo;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + crazy_interpolation, + "a { + @if true { + a: #{\"#{\"\\\\}}}{{{\"}#\"}; + } + + @if false { + a: #{\"#{\"\\\\}}}{{{\"}#\"}; + } @else if true { + b: #{\"#{\"\\\\}}}{{{\"}#\"}; + } + + @if false { + a: #{\"#{\"\\\\}}}{{{\"}#\"}; + } @else if false { + b: #{\"#{\"\\\\}}}{{{\"}#\"}; + } @else { + c: #{\"#{\"\\\\}}}{{{\"}#\"}; + } + }", + "a {\n a: \\}}}{{{#;\n b: \\}}}{{{#;\n c: \\}}}{{{#;\n}\n" +); +grass_test!( + multiline_comments_everywhere, + " /**/ @if /**/ false /**/ {} /**/ + /**/ + /**/ @else /**/ if /**/ false /**/ {} /**/ + /**/ + /**/ @else /**/ {} /**/ + /**/ ", + "/**/\n/**/\n/**/\n" +); +grass_test!( + parent_selector_is_null_at_root, + "@if & { + a { + color: red; + } + }", + "" +); +grass_error!( + nothing_after_escape, + "@if \\", + "Error: Expected expression." +); +grass_error!(unclosed_dbl_quote, "@if true \" {}", "Error: Expected \"."); +grass_error!(unclosed_sgl_quote, "@if true ' {}", "Error: Expected '."); +grass_error!( + unclosed_call_args, + "@if a({}", + "Error: Expected expression." +); +grass_error!(nothing_after_div, "@if a/", "Error: Expected expression."); +grass_error!(multiline_error, "@if \"\n\"{}", "Error: Expected \"."); +grass_error!( + nothing_after_i_after_else, + "@if true {} @else i", + "Error: expected \"{\"." +); +grass_error!( + invalid_toplevel_selector, + "@if true { & { } }", + "Error: Top-level selectors may not contain the parent selector \"&\"." +); diff --git a/css/parser/tests/grass_important.rs b/css/parser/tests/grass_important.rs new file mode 100644 index 000000000000..9702be4d7b1a --- /dev/null +++ b/css/parser/tests/grass_important.rs @@ -0,0 +1,33 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + keyword_important_lowercase, + "a {\n height: !important;\n}\n", + "a {\n height: !important;\n}\n" +); +grass_test!( + keyword_important_uppercase, + "a {\n height: !IMPORTANT;\n}\n", + "a {\n height: !important;\n}\n" +); +grass_test!( + keyword_important_at_start_of_list, + "a {\n height: !important 1 2 3;\n}\n", + "a {\n height: !important 1 2 3;\n}\n" +); +grass_test!( + keyword_important_at_end_of_list, + "a {\n height: 1 2 3 !important;\n}\n", + "a {\n height: 1 2 3 !important;\n}\n" +); +grass_test!( + keyword_important_inside_list, + "a {\n color: 1 2 !important 3 4;\n}\n", + "a {\n color: 1 2 !important 3 4;\n}\n" +); +grass_test!( + whitespace_after_exclamation, + "a {\n color: ! important;\n}\n", + "a {\n color: !important;\n}\n" +); diff --git a/css/parser/tests/grass_inspect.rs b/css/parser/tests/grass_inspect.rs new file mode 100644 index 000000000000..636f7c8d370e --- /dev/null +++ b/css/parser/tests/grass_inspect.rs @@ -0,0 +1,131 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + inspect_unquoted_string, + "a {\n color: inspect(foo)\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + inspect_dbl_quoted_string, + "a {\n color: inspect(\"foo\")\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + inspect_sgl_quoted_string, + "a {\n color: inspect(\"foo\")\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + inspect_unitless_number, + "a {\n color: inspect(1)\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + inspect_px_number, + "a {\n color: inspect(1px)\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + inspect_color_3_hex, + "a {\n color: inspect(#fff)\n}\n", + "a {\n color: #fff;\n}\n" +); +grass_test!( + inspect_color_6_hex, + "a {\n color: inspect(#ffffff)\n}\n", + "a {\n color: #ffffff;\n}\n" +); +grass_test!( + inspect_color_name, + "a {\n color: inspect(red)\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + inspect_true, + "a {\n color: inspect(true)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + inspect_false, + "a {\n color: inspect(false)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + inspect_null, + "a {\n color: inspect(null)\n}\n", + "a {\n color: null;\n}\n" +); +grass_test!( + inspect_empty_brackets, + "a {\n color: inspect([]);\n}\n", + "a {\n color: [];\n}\n" +); +grass_test!( + inspect_comma_separated_one_val, + "a {\n color: inspect((1, ));\n}\n", + "a {\n color: (1,);\n}\n" +); +grass_test!( + inspect_comma_separated_one_val_bracketed, + "a {\n color: inspect([1, ]);\n}\n", + "a {\n color: [1,];\n}\n" +); +grass_test!( + inspect_space_separated_one_val_bracketed, + "a {\n color: inspect(append((), 1, space));\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + inspect_list_of_empty_list, + "a {\n color: inspect(((), ()));\n}\n", + "a {\n color: (), ();\n}\n" +); +grass_test!( + #[ignore] + inspect_comma_separated_list_of_comma_separated_lists, + "a {\n color: inspect([(1, 2), (3, 4)]);\n}\n", + "a {\n color: [(1, 2), (3, 4)];\n}\n" +); +grass_test!( + inspect_empty_list, + "a {\n color: inspect(())\n}\n", + "a {\n color: ();\n}\n" +); +grass_test!( + inspect_spaced_list, + "a {\n color: inspect(1 2 3)\n}\n", + "a {\n color: 1 2 3;\n}\n" +); +grass_test!( + #[ignore] + inspect_comma_list, + "a {\n color: inspect(1, 2, 3)\n}\n", + "a {\n color: 1, 2, 3;\n}\n" +); +grass_test!( + inspect_parens, + "a {\n color: inspect((((a))));\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + inspect_mul_units, + "a {\n color: inspect(1em * 1px);\n}\n", + "a {\n color: 1em*px;\n}\n" +); +grass_test!( + inspect_map_with_map_key_and_value, + "a {\n color: inspect(((a: b): (c: d)));\n}\n", + "a {\n color: ((a: b): (c: d));\n}\n" +); +grass_test!( + inspect_map_in_arglist, + "@function foo($a...) { + @return inspect($a); + } + + a { + color: foo((a: b)); + }", + "a {\n color: ((a: b),);\n}\n" +); diff --git a/css/parser/tests/grass_interpolation.rs b/css/parser/tests/grass_interpolation.rs new file mode 100644 index 000000000000..0ed2a0bd54d0 --- /dev/null +++ b/css/parser/tests/grass_interpolation.rs @@ -0,0 +1,84 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + removes_double_quotes, + "a {\n color: #{\"red\"};\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + removes_single_quotes, + "a {\n color: #{'red'};\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + number_after_interpolation, + "a {\n color: a#{foo}1;\n}\n", + "a {\n color: afoo1;\n}\n" +); +grass_test!( + double_hyphen_before_interpolation, + "a {\n --#{foo}: red;\n}\n", + "a {\n --foo: red;\n}\n" +); +grass_test!( + preserves_inner_single_quotes, + "a {\n color: #{\"''\"};\n}\n", + "a {\n color: '';\n}\n" +); +grass_test!( + single_quotes_converted_to_double_when_interpolated, + "a {\n color: '#{foo}';\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + double_quotes_inside_double_quoted_string, + "a {\n color: #{\"#{'\"'}\"};\n}\n", + "a {\n color: \";\n}\n" +); +grass_test!( + unquotes_space_separated_list, + "a {\n color: #{\"a\" 'b'};\n}\n", + "a {\n color: a b;\n}\n" +); +grass_test!( + interpolated_newline, + "a {\n color: \"#{\"\\a\"}\";\n}\n", + "a {\n color: \"\\a\";\n}\n" +); +grass_test!( + double_interpolated_newline, + "a {\n color: \"#{#{\"\\a\"}}\";\n}\n", + "a {\n color: \"\\a\";\n}\n" +); +grass_test!( + interpolated_quoted_newline, + "a {\n color: #{\"\\a\"};\n}\n", + "a {\n color: ;\n}\n" +); +grass_test!( + interpolate_escaped_quotes, + "a {\n color: #{\\\"\\'};\n}\n", + "a {\n color: \\\"\\';\n}\n" +); +grass_test!( + interpolate_escaped_quotes_in_quotes, + "a {\n color: \"#{\\\"\\'}\";\n}\n", + "a {\n color: \"\\\\\\\"\\\\'\";\n}\n" +); +grass_test!( + interpolated_plain_css_fn, + "$f: foo;\na {\n color: #{$f}(a, 1+2, c);\n}\n", + "a {\n color: foo(a, 3, c);\n}\n" +); +grass_test!( + #[ignore = "we evaluate interpolation eagerly"] + interpolated_builtin_fn, + "a {\n color: uni#{t}less(1px);\n}\n", + "a {\n color: unitless(1px);\n}\n" +); +grass_error!( + error_message_when_at_start_of_value, + "a {\n color: #{2px*5px};\n}\n", + "Error: 10px*px isn't a valid CSS value." +); diff --git a/css/parser/tests/grass_is_superselector.rs b/css/parser/tests/grass_is_superselector.rs new file mode 100644 index 000000000000..bc47777d72a3 --- /dev/null +++ b/css/parser/tests/grass_is_superselector.rs @@ -0,0 +1,494 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + more_specific_class_compound, + "a {\n color: is-superselector(\".foo\", \".foo.bar\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + less_specific_class_compound, + "a {\n color: is-superselector(\".foo.bar\", \".foo\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + more_specific_class_complex, + "a {\n color: is-superselector(\".bar\", \".foo .bar\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + less_specific_class_complex, + "a {\n color: is-superselector(\".foo .bar\", \".bar\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + two_in_sub, + "a {\n color: is-superselector(\"c\", \"c, d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + two_in_super, + "a {\n color: is-superselector(\"c, d\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + two_in_both_equal, + "a {\n color: is-superselector(\"c, d\", \"c, d\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + two_in_both_subset, + "a {\n color: is-superselector(\"c, d\", \"c.e, d.f\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + two_in_both_superset, + "a {\n color: is-superselector(\"c.e, d.f\", \"c, d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + two_in_sub_satisfied_by_one_super, + "a {\n color: is-superselector(\".c\", \"d.c, e.c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + three_in_super_match_one, + "a {\n color: is-superselector(\"c, d, e\", \"d\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + three_in_super_match_two, + "a {\n color: is-superselector(\"c, d, e\", \"e, c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + three_in_super_match_three, + "a {\n color: is-superselector(\"c, d, e\", \"d, c, e\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + three_in_super_miss_one, + "a {\n color: is-superselector(\"c, d, e\", \"c, f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_attribute_equal, + "a {\n color: is-superselector(\"[c=d]\", \"[c=d]\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_attribute_different_attr, + "a {\n color: is-superselector(\"[c=d]\", \"[e=d]\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_attribute_different_val, + "a {\n color: is-superselector(\"[c=d]\", \"[c=e]\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_attribute_different_operator, + "a {\n color: is-superselector(\"[c=d]\", \"[c^=e]\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_class_equal, + "a {\n color: is-superselector(\".c\", \".c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_class_not_equal, + "a {\n color: is-superselector(\".c\", \".d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_id_equal, + "a {\n color: is-superselector(\"#c\", \"#c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_id_not_equal, + "a {\n color: is-superselector(\"#c\", \"#d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_placeholder_equal, + "a {\n color: is-superselector(\"%c\", \"%c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_placeholder_not_equal, + "a {\n color: is-superselector(\"%c\", \"%d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_equal, + "a {\n color: is-superselector(\"c\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_type_not_equal, + "a {\n color: is-superselector(\"c\", \"d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_and_universal, + "a {\n color: is-superselector(\"c\", \"*\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_equal, + "a {\n color: is-superselector(\"c|d\", \"c|d\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_type_different_explicit_namespace, + "a {\n color: is-superselector(\"c|d\", \"e|d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_implicit_namespace, + "a {\n color: is-superselector(\"c|d\", \"d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"c|d\", \"|d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_universal_namespace, + "a {\n color: is-superselector(\"c|d\", \"*|d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_explicit_namespace, + "a {\n color: is-superselector(\"|c\", \"d|c\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"|c\", \"|c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_explicit_namespace, + "a {\n color: is-superselector(\"*|c\", \"d|c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_implicit_namespace, + "a {\n color: is-superselector(\"*|c\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"*|c\", \"|c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_type_universal_namespace_and_universal_namespace, + "a {\n color: is-superselector(\"*|c\", \"*|c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_pseudo_no_args_equal, + "a {\n color: is-superselector(\":c\", \":c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_pseudo_no_args_different, + "a {\n color: is-superselector(\":c\", \":d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_no_args_class_and_element, + "a {\n color: is-superselector(\":c\", \"::c\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_no_args_element_and_element_equal, + "a {\n color: is-superselector(\"::c\", \"::c\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_pseudo_no_args_element_and_element_different, + "a {\n color: is-superselector(\"::c\", \"::d\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_no_args_element_and_class, + "a {\n color: is-superselector(\"::c\", \":c\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_equal, + "a {\n color: is-superselector(\":c(@#$)\", \":c(@#$)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_different_name, + "a {\n color: is-superselector(\":c(@#$)\", \":d(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_different_arg, + "a {\n color: is-superselector(\":c(@#$)\", \":d(*&^)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_different_no_arg, + "a {\n color: is-superselector(\":c(@#$)\", \":c\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_and_element, + "a {\n color: is-superselector(\":c(@#$)\", \"::c(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_element_and_element_equal, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c(@#$)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + simple_pseudo_arg_element_and_element_different_name, + "a {\n color: is-superselector(\"::c(@#$)\", \"::d(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_element_and_element_different_arg, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c(*&^)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_element_and_element_different_no_arg, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + simple_pseudo_arg_element_and_class, + "a {\n color: is-superselector(\"::c(@#$)\", \":c(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_any_superset, + "a {\n color: is-superselector(\":any(c d, e f, g h)\", \":any(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_any_subset, + "a {\n color: is-superselector(\":any(c d.i, e j f)\", \":any(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_any_prefix_superset, + "a {\n color: is-superselector(\":-pfx-any(c d, e f, g h)\", \":-pfx-any(c d.i, e j \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_any_prefix_subset, + "a {\n color: is-superselector(\":-pfx-any(c d.i, e j f)\", \":-pfx-any(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_superset, + "a {\n color: is-superselector(\":current(c d, e f, g h)\", \":current(c d.i, e j f)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_subset, + "a {\n color: is-superselector(\":current(c d.i, e j f)\", \":current(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_equal, + "a {\n color: is-superselector(\":current(c d, e f)\", \":current(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_current_bare_sub, + "a {\n color: is-superselector(\":current(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_prefix_superset, + "a {\n color: is-superselector(\":-pfx-current(c d, e f, g h)\", \":-pfx-current(c d.i, e j \ + f)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_prefix_subset, + "a {\n color: is-superselector(\":-pfx-current(c d.i, e j f)\", \":-pfx-current(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_current_prefix_equal, + "a {\n color: is-superselector(\":-pfx-current(c d, e f)\", \":-pfx-current(c d, e \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_has_superset, + "a {\n color: is-superselector(\":has(c d, e f, g h)\", \":has(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_has_subset, + "a {\n color: is-superselector(\":has(c d.i, e j f)\", \":has(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_has_equal, + "a {\n color: is-superselector(\":has(c d, e f)\", \":has(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_has_bare_sub, + "a {\n color: is-superselector(\":has(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_has_prefix_superset, + "a {\n color: is-superselector(\":-pfx-has(c d, e f, g h)\", \":-pfx-has(c d.i, e j \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_has_prefix_subset, + "a {\n color: is-superselector(\":-pfx-has(c d.i, e j f)\", \":-pfx-has(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_has_prefix_equal, + "a {\n color: is-superselector(\":-pfx-has(c d, e f)\", \":-pfx-has(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_superset, + "a {\n color: is-superselector(\":host(c d, e f, g h)\", \":host(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_subset, + "a {\n color: is-superselector(\":host(c d.i, e j f)\", \":host(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_equal, + "a {\n color: is-superselector(\":host(c d, e f)\", \":host(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_bare_sub, + "a {\n color: is-superselector(\":host(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_prefix_superset, + "a {\n color: is-superselector(\":-pfx-host(c d, e f, g h)\", \":-pfx-host(c d.i, e j \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_prefix_subset, + "a {\n color: is-superselector(\":-pfx-host(c d.i, e j f)\", \":-pfx-host(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_prefix_equal, + "a {\n color: is-superselector(\":-pfx-host(c d, e f)\", \":-pfx-host(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_context_superset, + "a {\n color: is-superselector(\":host-context(c d, e f, g h)\", \":host-context(c d.i, e j \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_context_subset, + "a {\n color: is-superselector(\":host-context(c d.i, e j f)\", \":host-context(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_context_equal, + "a {\n color: is-superselector(\":host-context(c d, e f)\", \":host-context(c d, e \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_context_bare_sub, + "a {\n color: is-superselector(\":host-context(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_context_prefix_superset, + "a {\n color: is-superselector(\":-pfx-host-context(c d, e f, g h)\", \":-pfx-host-context(c \ + d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_host_context_prefix_subset, + "a {\n color: is-superselector(\":-pfx-host-context(c d.i, e j f)\", \":-pfx-host-context(c \ + d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_host_context_prefix_equal, + "a {\n color: is-superselector(\":-pfx-host-context(c d, e f)\", \":-pfx-host-context(c d, e \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_slotted_superset, + "a {\n color: is-superselector(\"::slotted(c d, e f, g h)\", \"::slotted(c d.i, e j \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_slotted_subset, + "a {\n color: is-superselector(\"::slotted(c d.i, e j f)\", \"::slotted(c d, e f, g \ + h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_slotted_equal, + "a {\n color: is-superselector(\"::slotted(c d, e f)\", \"::slotted(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_slotted_bare_sub, + "a {\n color: is-superselector(\"::slotted(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_slotted_prefix_superset, + "a {\n color: is-superselector(\"::-pfx-slotted(c d, e f, g h)\", \"::-pfx-slotted(c d.i, e \ + j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + psuedo_slotted_prefix_subset, + "a {\n color: is-superselector(\"::-pfx-slotted(c d.i, e j f)\", \"::-pfx-slotted(c d, e f, \ + g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + psuedo_slotted_prefix_equal, + "a {\n color: is-superselector(\"::-pfx-slotted(c d, e f)\", \"::-pfx-slotted(c d, e \ + f)\");\n}\n", + "a {\n color: true;\n}\n" +); + +// todo: /spec/core_functions/selector/is_superselector/simple/pseudo/ +// selector_arg/ :not, :matches, :nth-child, :nth-last-child diff --git a/css/parser/tests/grass_keyframes.rs b/css/parser/tests/grass_keyframes.rs new file mode 100644 index 000000000000..efb232af47c6 --- /dev/null +++ b/css/parser/tests/grass_keyframes.rs @@ -0,0 +1,143 @@ +#[macro_use] +mod grass_macros; + +// @content inside keyframes +grass_test!( + content_inside_keyframes, + "@mixin foo { + @keyframes { + @content; + } + } + a { + @include foo { + color: red; + }; + }", + "@keyframes {\n color: red;\n}\n" +); + +grass_test!( + empty_keyframes_is_emitted_exact, + "@keyframes {}", + "@keyframes {}\n" +); +grass_test!( + keyframes_is_at_root, + "a {\n @keyframes {}\n}\n", + "@keyframes {}\n" +); +grass_test!( + keyframes_inside_ruleset_with_other_styles, + "a { + color: red; + @keyframes {} + color: green; + }", + "a {\n color: red;\n color: green;\n}\n@keyframes {}\n" +); +grass_test!( + keyframes_lowercase_to, + "@keyframes {to {color: red;}}", + "@keyframes {\n to {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_lowercase_from, + "@keyframes {from {color: red;}}", + "@keyframes {\n from {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_uppercase_to, + "@keyframes {TO {color: red;}}", + "@keyframes {\n to {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_uppercase_from, + "@keyframes {FROM {color: red;}}", + "@keyframes {\n from {\n color: red;\n }\n}\n" +); +grass_error!( + keyframes_invalid_selector_beginning_with_f, + "@keyframes {foo {}}", + "Error: Expected \"to\" or \"from\"." +); +grass_error!( + keyframes_invalid_selector_beginning_with_t, + "@keyframes {too {}}", + "Error: Expected \"to\" or \"from\"." +); +grass_error!( + keyframes_invalid_selector_beginning_with_ascii_char, + "@keyframes {a {}}", + "Error: Expected \"to\" or \"from\"." +); +grass_error!( + keyframes_invalid_selector_number_missing_percent, + "@keyframes {10 {}}", + "Error: expected \"%\"." +); +grass_test!( + keyframes_simple_percent_selector, + "@keyframes {0% {color: red;}}", + "@keyframes {\n 0% {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_comma_separated_percent_selectors, + "@keyframes {0%, 5%, 10%, 15% {color: red;}}", + "@keyframes {\n 0%, 5%, 10%, 15% {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_empty_with_name, + "@keyframes foo {}", + "@keyframes foo {}\n" +); +grass_test!( + keyframes_variable_in_name, + "@keyframes $foo {}", + "@keyframes $foo {}\n" +); +grass_test!( + keyframes_arithmetic_in_name, + "@keyframes 1 + 2 {}", + "@keyframes 1 + 2 {}\n" +); +grass_test!( + keyframes_interpolation_in_name, + "@keyframes #{1 + 2} {}", + "@keyframes 3 {}\n" +); +grass_test!( + keyframes_contains_multiline_comment, + "@keyframes foo {/**/}", + "@keyframes foo {\n /**/\n}\n" +); +grass_test!( + keyframes_multiple_rulesets, + "@keyframes { + to { + color: red; + } + from { + color: green; + } + }", + "@keyframes {\n to {\n color: red;\n }\n from {\n color: green;\n }\n}\n" +); +grass_test!( + keyframes_vendor_prefix, + "@-webkit-keyframes foo { + 0% { + color: red; + } + }", + "@-webkit-keyframes foo {\n 0% {\n color: red;\n }\n}\n" +); +grass_test!( + keyframes_allow_decimal_selector, + "@keyframes foo { + 12.5% { + color: red; + } + }", + "@keyframes foo {\n 12.5% {\n color: red;\n }\n}\n" +); diff --git a/css/parser/tests/grass_list.rs b/css/parser/tests/grass_list.rs new file mode 100644 index 000000000000..98018f8eba8f --- /dev/null +++ b/css/parser/tests/grass_list.rs @@ -0,0 +1,383 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + length_of_list_as_var, + "$a: 1 2 3 4 5;a {\n color: length($a);\n}\n", + "a {\n color: 5;\n}\n" +); +grass_test!( + length_of_list, + "a {\n color: length(1 2 3 4 5);\n}\n", + "a {\n color: 5;\n}\n" +); +grass_test!( + length_of_comma_list, + "a {\n color: length((1, 2, 3, 4, 5));\n}\n", + "a {\n color: 5;\n}\n" +); +grass_test!( + nth_space_separated, + "a {\n color: nth(a b c, 1);\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + nth_negative_index, + "a {\n color: nth(a b c, -2);\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + nth_comma_separated, + "a {\n color: nth((a, b, c), 3);\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + nth_non_list, + "a {\n color: nth(foo, 1);\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + nth_map, + "a {\n color: nth((c: d, e: f, g: h), 2);\n}\n", + "a {\n color: e f;\n}\n" +); +grass_test!( + list_separator_space_separated, + "a {\n color: list-separator(a b c);\n}\n", + "a {\n color: space;\n}\n" +); +grass_test!( + list_separator_foo, + "a {\n color: list-separator(foo);\n}\n", + "a {\n color: space;\n}\n" +); +grass_test!( + list_separator_comma_separated, + "a {\n color: list-separator((a, b, c));\n}\n", + "a {\n color: comma;\n}\n" +); +grass_test!( + list_separator_comma_separated_with_space, + "a {\n color: list-separator(((a b, c d)));\n}\n", + "a {\n color: comma;\n}\n" +); +grass_test!( + list_separator_map, + "a {\n color: list-separator((a: b, c: d));\n}\n", + "a {\n color: comma;\n}\n" +); +grass_test!( + list_separator_arglist, + "@mixin foo($arg...) { + color: list-separator($arg); + } + + a { + @include foo(1, 2, 3); + }", + "a {\n color: comma;\n}\n" +); +grass_test!( + list_separator_empty, + "a {\n color: list-separator(());\n}\n", + "a {\n color: space;\n}\n" +); +grass_test!( + set_nth_named_args, + "a {\n color: set-nth($list: 1 2 3, $n: 2, $value: foo);\n}\n", + "a {\n color: 1 foo 3;\n}\n" +); +grass_test!( + set_nth_non_list, + "a {\n color: set-nth(c, 1, e);\n}\n", + "a {\n color: e;\n}\n" +); +grass_test!( + set_nth_2_long, + "a {\n color: set-nth(c d, 1, e);\n}\n", + "a {\n color: e d;\n}\n" +); +grass_test!( + set_nth_comma_separated, + "a {\n color: set-nth((a, b, c), 1, e);\n}\n", + "a {\n color: e, b, c;\n}\n" +); +grass_test!( + set_nth_bracketed, + "a {\n color: set-nth([a, b, c], 1, e);\n}\n", + "a {\n color: [e, b, c];\n}\n" +); +grass_test!( + set_nth_map, + "a {\n color: set-nth((c: d, e: f, g: h), 2, i);\n}\n", + "a {\n color: c d, i, g h;\n}\n" +); +grass_test!( + set_nth_arglist, + "@mixin foo($args...) { + color: set-nth($args, 2, 0); + } + + a { + @include foo(1, 2, 3, 4); + }", + "a {\n color: 1, 0, 3, 4;\n}\n" +); +grass_test!( + append_space_separated, + "a {\n color: append(a b, c);\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + append_comma_separated, + "a {\n color: append((a, b), c);\n}\n", + "a {\n color: a, b, c;\n}\n" +); +grass_test!( + append_list, + "a {\n color: append(a b, c d);\n}\n", + "a {\n color: a b c d;\n}\n" +); +grass_test!( + append_list_separator_comma, + "a {\n color: append(a, b, comma);\n}\n", + "a {\n color: a, b;\n}\n" +); +grass_test!( + append_list_separator_space, + "a {\n color: append((a, b), c, space);\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + append_empty, + "a {\n color: append((), a);\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + append_bracketed, + "a {\n color: append([], 1);\n}\n", + "a {\n color: [1];\n}\n" +); +grass_error!( + append_non_string_separator, + "a {b: append(c, d, $separator: 1);}", + "Error: $separator: 1 is not a string." +); +grass_test!( + join_space_separated, + "a {\n color: join(a b, c d);\n}\n", + "a {\n color: a b c d;\n}\n" +); +grass_test!( + join_comma_separated, + "a {\n color: join((a, b), (c, d));\n}\n", + "a {\n color: a, b, c, d;\n}\n" +); +grass_test!( + join_non_list, + "a {\n color: join(a, b);\n}\n", + "a {\n color: a b;\n}\n" +); +grass_test!( + join_separator_comma, + "a {\n color: join(a, b, comma);\n}\n", + "a {\n color: a, b;\n}\n" +); +grass_test!( + join_separator_space, + "a {\n color: join((a, b), (c, d), space);\n}\n", + "a {\n color: a b c d;\n}\n" +); +grass_test!( + join_first_bracketed, + "a {\n color: join([a], b);\n}\n", + "a {\n color: [a b];\n}\n" +); +grass_test!( + join_second_bracketed, + "a {\n color: join(a, [b]);\n}\n", + "a {\n color: a b;\n}\n" +); +grass_test!( + join_space_comma, + "a {\n color: join(a b, (c,));\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + join_comma_space, + "a {\n color: join((a, b), (c));\n}\n", + "a {\n color: a, b, c;\n}\n" +); +grass_test!( + join_bracketed_null, + "a {\n color: join([a b], c, $bracketed: null);\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + join_bracketed_false, + "a {\n color: join([a b], c, $bracketed: false);\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + join_bracketed_auto_brackets, + "a {\n color: join([a b], c, $bracketed: auto);\n}\n", + "a {\n color: [a b c];\n}\n" +); +grass_test!( + join_bracketed_auto_none, + "a {\n color: join(a b, c, $bracketed: auto);\n}\n", + "a {\n color: a b c;\n}\n" +); +grass_test!( + join_bracketed_random_string, + "a {\n color: join(a b, c, $bracketed: afhsihsdhsdkhsd);\n}\n", + "a {\n color: [a b c];\n}\n" +); +grass_test!( + join_empty_first_takes_second_list_separator, + "a {\n color: join((), (a, b, c));\n}\n", + "a {\n color: a, b, c;\n}\n" +); +grass_test!( + join_single_comma_both, + "a {\n color: join((a,), (b,));\n}\n", + "a {\n color: a, b;\n}\n" +); +grass_test!( + join_two_maps, + "a {\n color: join((c: d, e: f), (g: h, i: j));\n}\n", + "a {\n color: c d, e f, g h, i j;\n}\n" +); +grass_test!( + join_non_list_first_takes_separator_of_second, + "a {\n color: join(c, (d, e));\n}\n", + "a {\n color: c, d, e;\n}\n" +); +grass_test!( + join_single_bracketed_first_takes_separator_of_second, + "a {\n color: join([a], (b, ));\n}\n", + "a {\n color: [a, b];\n}\n" +); +grass_test!(bracketed_ident, "a {\n color: [a];\n}\n"); +grass_test!(bracketed_space_list, "a {\n color: [a b];\n}\n"); +grass_test!(bracketed_comma_list, "a {\n color: [a, b];\n}\n"); +grass_test!(bracketed_as_space_list, "a {\n color: [a b] c;\n}\n"); +grass_test!( + trailing_comma_bare, + "a {\n color: 1,;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + trailing_comma_paren, + "a {\n color: (1,);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + trailing_comma_bracket, + "a {\n color: [1,];\n}\n", + "a {\n color: [1];\n}\n" +); +grass_test!( + null_values_in_list_ommitted, + "a {\n color: 1, null, null;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + removes_paren_around_space_list, + "a {\n color: (foo bar);\n}\n", + "a {\n color: foo bar;\n}\n" +); +grass_test!( + removes_paren_around_item_in_list, + "a {\n color: 1 (foo bar);\n}\n", + "a {\n color: 1 foo bar;\n}\n" +); +grass_test!( + long_space_separated_list, + "a {\n color: a b c d e f g h i j k l m n o p q r s t u v w x y z;\n}\n" +); +grass_test!( + long_comma_separated_list, + "a {\n color: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, \ + z;\n}\n" +); +grass_test!( + deeply_nested_square_braces, + "a {\n color: [[[[[[a]]]]]];\n}\n" +); +grass_test!( + index_found_space_separated, + "a {\n color: index(1px solid red, solid);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + index_not_found_space_separated, + "a {\n color: index(1px solid red, dashed);\n}\n", + "" +); +grass_test!( + index_found_map, + "a {\n color: index((width: 10px, height: 20px), (height 20px));\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + index_unit_conversions, + "a {\n color: index(1px 1in 1cm, 96px);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + zip_three, + "a {\n color: zip(1px 1px 3px, solid dashed solid, red green blue);\n}\n", + "a {\n color: 1px solid red, 1px dashed green, 3px solid blue;\n}\n" +); +grass_test!( + zip_parens, + "a {\n color: zip((a, b, c));\n}\n", + "a {\n color: a, b, c;\n}\n" +); +grass_test!( + zip_parens_length, + "a {\n color: length(zip((a, b, c)));\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!(empty_bracketed_list, "a {\n empty: [];\n}\n"); +grass_test!( + empty_bracketed_list_equality, + "a {\n color: []==[];\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + empty_bracketed_list_whitespace, + "a {\n color: [ /**/ ];\n}\n", + "a {\n color: [];\n}\n" +); +grass_error!( + invalid_item_in_space_separated_list, + "a {\n color: red color * #abc;\n}\n", + "Error: Undefined operation \"color * #abc\"." +); +grass_error!( + invalid_item_in_comma_separated_list, + "a {\n color: red, color * #abc;\n}\n", + "Error: Undefined operation \"color * #abc\"." +); +grass_error!( + invalid_item_in_space_separated_list_inside_interpolation, + "a {\n color: #{red color * #abc};\n}\n", + "Error: Undefined operation \"color * #abc\"." +); +grass_error!( + invalid_item_in_comma_separated_list_inside_interpolation, + "a {\n color: #{red, color * #abc};\n}\n", + "Error: Undefined operation \"color * #abc\"." +); +grass_error!( + nth_invalid_index_message_contains_unit, + "a {\n color: nth([], 1px);\n}\n", + "Error: $n: Invalid index 1px for a list with 0 elements." +); +grass_error!( + set_nth_invalid_index_message_contains_unit, + "a {\n color: set-nth([], 1px, a);\n}\n", + "Error: $n: Invalid index 1px for a list with 0 elements." +); diff --git a/css/parser/tests/grass_macros/mod.rs b/css/parser/tests/grass_macros/mod.rs new file mode 100644 index 000000000000..6957efbeec80 --- /dev/null +++ b/css/parser/tests/grass_macros/mod.rs @@ -0,0 +1,92 @@ +#![allow(dead_code)] + +use swc_common::FileName; + +#[macro_export] +macro_rules! grass_test { + ($( #[$attr:meta] ),*$func:ident, $input:expr) => { + $(#[$attr])* + #[test] + #[allow(non_snake_case)] + fn $func() { + crate::grass_macros::test(stringify!($func), $input); + } + }; + ($( #[$attr:meta] ),*$func:ident, $input:expr, $output:expr) => { + $(#[$attr])* + #[test] + #[allow(non_snake_case)] + fn $func() { + crate::grass_macros::test(stringify!($func), $output); + } + }; +} + +pub fn test(name: &str, s: &str) { + println!( + "===== ===== ===== ===== =====\n{}\n===== ===== ===== ===== =====", + s + ); + + ::testing::run_test2(false, |cm, _handler| { + let fm = cm.new_source_file(FileName::Real(name.to_string().into()), s.to_string()); + + let res = swc_css_parser::parse(fm.start_pos, &fm.src); + res.unwrap(); + + // TODO: Print ast to file + + Ok(()) + }) + .unwrap(); +} + +pub fn error(name: &str, s: &str) { + println!( + "===== ===== ===== ===== =====\n{}\n===== ===== ===== ===== =====", + s + ); + + ::testing::run_test2(false, |cm, _handler| { + let fm = cm.new_source_file(FileName::Real(name.to_string().into()), s.to_string()); + + let res = swc_css_parser::parse(fm.start_pos, &fm.src); + res.unwrap_err(); + + // TODO: Print error message to file + + Ok(()) + }) + .unwrap(); +} + +/// Verify the error *message* +/// Span and scope information are not yet tested +#[macro_export] +macro_rules! grass_error { + ($( #[$attr:meta] ),*$func:ident, $input:expr, $err:expr) => { + $(#[$attr])* + #[test] + #[allow(non_snake_case)] + fn $func() { + crate::grass_macros::error(stringify!($func), $input); + } + }; +} + +#[macro_export] +macro_rules! assert_err { + ($err:literal, $input:expr) => { + match grass::from_string($input.to_string(), &grass::Options::default()) { + Ok(..) => panic!("did not fail"), + Err(e) => assert_eq!( + $err, + e.to_string() + .chars() + .take_while(|c| *c != '\n') + .collect::() + .as_str() + ), + } + }; +} diff --git a/css/parser/tests/grass_map.rs b/css/parser/tests/grass_map.rs new file mode 100644 index 000000000000..ad0f192080c3 --- /dev/null +++ b/css/parser/tests/grass_map.rs @@ -0,0 +1,246 @@ +#[macro_use] +mod grass_macros; +grass_test!( + map_get_key_exists, + "a {\n color: map-get((a: b), a);\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + map_get_key_does_not_exist, + "a {\n color: map-get((a: b), foo);\n}\n", + "" +); +grass_test!( + map_get_empty_list, + "a {\n color: map-get((), foo);\n}\n", + "" +); +grass_error!( + map_get_non_map, + "a {\n color: map-get(foo, foo);\n}\n", + "Error: $map: foo is not a map." +); +grass_error!( + map_get_one_arg, + "a {\n color: map-get(1);\n}\n", + "Error: Missing argument $key." +); +grass_test!( + map_has_key_true, + "a {\n color: map-has-key((a: b), a);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + map_has_key_false, + "a {\n color: map-has-key((a: b), foo);\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + map_has_key_empty_list, + "a {\n color: map-has-key((), foo);\n}\n", + "a {\n color: false;\n}\n" +); +grass_error!( + map_has_key_non_map, + "a {\n color: map-has-key(foo, foo);\n}\n", + "Error: $map: foo is not a map." +); +grass_test!( + map_keys_one, + "a {\n color: map-keys((a: b));\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + map_keys_are_comma_separated, + "a {\n color: map-keys((a: b, c: d));\n}\n", + "a {\n color: a, c;\n}\n" +); +grass_test!( + map_keys_empty, + "a {\n color: inspect(map-keys(()));\n}\n", + "a {\n color: ();\n}\n" +); +grass_error!( + map_keys_non_map, + "a {\n color: map-keys(foo);\n}\n", + "Error: $map: foo is not a map." +); +grass_test!( + map_values_one, + "a {\n color: map-values((a: b));\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + map_values_empty, + "a {\n color: inspect(map-values(()));\n}\n", + "a {\n color: ();\n}\n" +); +grass_test!( + map_values_are_comma_separated, + "a {\n color: map-values((a: b, c: d));\n}\n", + "a {\n color: b, d;\n}\n" +); +grass_error!( + map_values_non_map, + "a {\n color: map-values(foo);\n}\n", + "Error: $map: foo is not a map." +); +grass_test!( + map_merge_one, + "a {\n color: inspect(map-merge((a: b), (c: d)));\n}\n", + "a {\n color: (a: b, c: d);\n}\n" +); +grass_test!( + map_merge_both_empty, + "a {\n color: inspect(map-merge((), ()));\n}\n", + "a {\n color: ();\n}\n" +); +grass_test!( + map_merge_same_keys, + "a {\n color: inspect(map-merge((c: d, e: f), (c: 1, e: 2)));\n}\n", + "a {\n color: (c: 1, e: 2);\n}\n" +); +grass_error!( + map_merge_map1_non_map, + "a {\n color: map-merge(foo, (a: b));\n}\n", + "Error: $map1: foo is not a map." +); +grass_error!( + map_merge_map2_non_map, + "a {\n color: map-merge((a: b), foo);\n}\n", + "Error: $map2: foo is not a map." +); +grass_test!( + map_dbl_quoted_key, + "a {\n color: map-get((\"a\": b), \"a\");\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + map_key_quoting_ignored, + "a {\n color: map-get((\"a\": b), 'a');\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + map_arbitrary_number_of_entries, + "a {\n color: inspect((a: b, c: d, e: f, g: h, i: j, h: k, l: m, n: o));\n}\n", + "a {\n color: (a: b, c: d, e: f, g: h, i: j, h: k, l: m, n: o);\n}\n" +); +grass_test!( + map_length, + "a {\n color: length((a: b, c: d, e: f));\n}\n", + "a {\n color: 3;\n}\n" +); +grass_error!( + map_has_key_one_arg, + "a {\n color: map-has-key(1);\n}\n", + "Error: Missing argument $key." +); +grass_test!( + map_remove_one, + "a {\n color: inspect(map-remove((\"foo\": 1, \"bar\": 2), \"bar\"));\n}\n", + "a {\n color: (\"foo\": 1);\n}\n" +); +grass_test!( + map_remove_two, + "a {\n color: inspect(map-remove((\"foo\": 1, \"bar\": 2, \"baz\": 3), \"bar\", \ + \"baz\"));\n}\n", + "a {\n color: (\"foo\": 1);\n}\n" +); +grass_test!( + map_remove_empty_list, + "a {\n color: inspect(map-remove((), foo));\n}\n", + "a {\n color: ();\n}\n" +); +grass_error!( + duplicate_key_in_declaration, + "a {\n $a: (foo: a, foo: b);\n}\n", + "Error: Duplicate key." +); +grass_error!( + display_map, + "a {\n color: (a: b, c: d);\n}\n", + "Error: (a: b, c: d) isn't a valid CSS value." +); +grass_test!( + map_comma_separated_list_as_key, + "a {\n color: map-keys(((1, 2): 3));\n}\n", + "a {\n color: 1, 2;\n}\n" +); +grass_test!( + #[ignore = "blocked on rewriting inspect"] + map_inspect_comma_separated_list_as_key, + "a {\n color: inspect(((1, 2): 3));\n}\n", + "a {\n color: ((1, 2): 3);\n}\n" +); +grass_test!( + // todo: this just tests that it compiles, but does not test + // if it parses correctly + map_with_map_as_value, + "$foo: (\"21by9\": (x: 21, y: 9));", + "" +); +grass_test!( + // todo: this just tests that it compiles, but does not test + // if it parses correctly + paren_with_paren_element_and_trailing_comma, + "$foo: ((\"<\", \"%3c\"), );", + "" +); +grass_test!( + map_with_whitespace_after_trailing_comma, + "$a: (foo: red, ); a {\n color: inspect($a);\n}\n", + "a {\n color: (foo: red);\n}\n" +); +grass_test!( + map_merge_not_exactly_equal, + "a {\n color: inspect(map-merge((0cm: a), (0mm: b)));\n}\n", + "a {\n color: (0cm: b);\n}\n" +); +grass_test!( + map_equality_is_independent_of_order, + "a {\n color: (c: d, a: b)==(a: b, c: d);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + map_equality_considers_both_key_and_value, + "a {\n color: (a: b)==(a: c);\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + empty_with_single_line_comments, + "$foo: (\n \n // :/a.b\n \n ); + a { + color: inspect($foo); + }", + "a {\n color: ();\n}\n" +); +grass_test!( + trailing_comma_in_doubly_nested_map, + r#"$a: ( + foo: ( + a: b, + c: d, + ) + );"#, + "" +); +grass_error!( + second_map_value_missing_colon, + "a {\n color: (a: b, c", + "Error: expected \":\"." +); +grass_error!( + second_map_value_missing_closing_paren, + "$a: (a: b, c: d", + "Error: expected \")\"." +); +grass_error!( + first_map_value_missing_closing_paren, + "$a: (a: b", + "Error: expected \")\"." +); +grass_error!( + denies_comma_separated_list_without_parens_as_key, + "$map: (a: 1, b, c, d: e);", + "Error: expected \":\"." +); diff --git a/css/parser/tests/grass_math.rs b/css/parser/tests/grass_math.rs new file mode 100644 index 000000000000..314fd015572b --- /dev/null +++ b/css/parser/tests/grass_math.rs @@ -0,0 +1,113 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + percentage_decimal, + "a {\n color: percentage(0.2);\n}\n", + "a {\n color: 20%;\n}\n" +); +grass_test!( + percentage_division, + "a {\n color: percentage(100px / 50px);\n}\n", + "a {\n color: 200%;\n}\n" +); +grass_test!( + integer_division, + "a {\n color: percentage(2);\n}\n", + "a {\n color: 200%;\n}\n" +); +grass_test!( + rounds_down, + "a {\n color: round(10.4px);\n}\n", + "a {\n color: 10px;\n}\n" +); +grass_test!( + rounds_up, + "a {\n color: round(10.6px);\n}\n", + "a {\n color: 11px;\n}\n" +); +grass_test!( + floor_below_pt_5, + "a {\n color: floor(10.4px);\n}\n", + "a {\n color: 10px;\n}\n" +); +grass_test!( + floor_above_pt_5, + "a {\n color: floor(10.6px);\n}\n", + "a {\n color: 10px;\n}\n" +); +grass_test!( + floor_big_int, + "a {\n color: floor(1.000000000000000001);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + ceil_below_pt_5, + "a {\n color: ceil(10.4px);\n}\n", + "a {\n color: 11px;\n}\n" +); +grass_test!( + ceil_above_pt_5, + "a {\n color: ceil(10.6px);\n}\n", + "a {\n color: 11px;\n}\n" +); +grass_test!( + ceil_big_int, + "a {\n color: ceil(1.000000000000000001);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + abs_positive, + "a {\n color: abs(10);\n}\n", + "a {\n color: 10;\n}\n" +); +grass_test!( + abs_negative, + "a {\n color: abs(-10);\n}\n", + "a {\n color: 10;\n}\n" +); +grass_test!( + abs_unit, + "a {\n color: abs(-10px);\n}\n", + "a {\n color: 10px;\n}\n" +); +grass_test!( + comparable_unitless, + "a {\n color: comparable(1, 2);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + comparable_none_px, + "a {\n color: comparable(1, 2px);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + comparable_px_px, + "a {\n color: comparable(1px, 2px);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + comparable_absolute, + "a {\n color: comparable(1px, 2in);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + comparable_absolute_font_relative, + "a {\n color: comparable(1px, 2em);\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + comparable_named, + "a {\n color: comparable($number1: 1, $number2: 2);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + random_limit_one, + "a {\n color: random(1);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + random_limit_big_one, + "a {\n color: random(1000000000000000001 - 1000000000000000000);\n}\n", + "a {\n color: 1;\n}\n" +); diff --git a/css/parser/tests/grass_math_module.rs b/css/parser/tests/grass_math_module.rs new file mode 100644 index 000000000000..9e44897426c9 --- /dev/null +++ b/css/parser/tests/grass_math_module.rs @@ -0,0 +1,598 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + clamp_in_the_middle, + "@use 'sass:math';\na {\n color: math.clamp(0, 1, 2);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + clamp_first_is_bigger, + "@use 'sass:math';\na {\n color: math.clamp(2, 1, 0);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + clamp_all_same_unit, + "@use 'sass:math';\na {\n color: math.clamp(0px, 1px, 2px);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + clamp_all_different_but_compatible_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2in);\n}\n", + "a {\n color: 1cm;\n}\n" +); +grass_error!( + clamp_only_min_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0, 1cm, 2in);\n}\n", + "Error: $min is unitless but $number has unit cm. Arguments must all have units or all be \ + unitless." +); +grass_error!( + clamp_only_number_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1, 2in);\n}\n", + "Error: $min has unit mm but $number is unitless. Arguments must all have units or all be \ + unitless." +); +grass_error!( + clamp_only_max_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2);\n}\n", + "Error: $min has unit mm but $max is unitless. Arguments must all have units or all be \ + unitless." +); +grass_test!( + sqrt_zero, + "@use 'sass:math';\na {\n color: math.sqrt(0);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + sqrt_small_positive, + "@use 'sass:math';\na {\n color: math.sqrt(99);\n}\n", + "a {\n color: 9.9498743711;\n}\n" +); +grass_test!( + sqrt_small_negative, + "@use 'sass:math';\na {\n color: math.sqrt(-99);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + sqrt_big_positive, + "@use 'sass:math';\na {\n color: \ + math.sqrt(9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: 3162277660168379038695424;\n}\n" +); +grass_test!( + sqrt_big_negative, + "@use 'sass:math';\na {\n color: \ + math.sqrt(-9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + sqrt_irrational, + "@use 'sass:math';\na {\n color: math.sqrt(2);\n}\n", + "a {\n color: 1.4142135624;\n}\n" +); +grass_test!( + sqrt_of_nan, + "@use 'sass:math';\na {\n color: math.sqrt((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_error!( + sqrt_with_units, + "@use 'sass:math';\na {\n color: math.sqrt(1px);\n}\n", + "Error: $number: Expected 1px to have no units." +); +grass_error!( + cos_non_angle, + "@use 'sass:math';\na {\n color: math.cos(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +grass_test!( + cos_small_degree, + "@use 'sass:math';\na {\n color: math.cos(1deg);\n}\n", + "a {\n color: 0.9998476952;\n}\n" +); +grass_test!( + cos_small_radian, + "@use 'sass:math';\na {\n color: math.cos(1rad);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +grass_test!( + cos_small_no_unit, + "@use 'sass:math';\na {\n color: math.cos(1);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +grass_test!( + cos_small_negative_degree, + "@use 'sass:math';\na {\n color: math.cos(-1deg);\n}\n", + "a {\n color: 0.9998476952;\n}\n" +); +grass_test!( + cos_small_negative_radian, + "@use 'sass:math';\na {\n color: math.cos(-1rad);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +grass_test!( + cos_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.cos(-1);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +grass_test!( + cos_pi, + "@use 'sass:math';\na {\n color: math.cos(math.$pi);\n}\n", + "a {\n color: -1;\n}\n" +); +grass_test!( + cos_two_pi, + "@use 'sass:math';\na {\n color: math.cos(2 * math.$pi);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_error!( + sin_non_angle, + "@use 'sass:math';\na {\n color: math.sin(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +grass_test!( + sin_small_degree, + "@use 'sass:math';\na {\n color: math.sin(1deg);\n}\n", + "a {\n color: 0.0174524064;\n}\n" +); +grass_test!( + sin_small_radian, + "@use 'sass:math';\na {\n color: math.sin(1rad);\n}\n", + "a {\n color: 0.8414709848;\n}\n" +); +grass_test!( + sin_small_no_unit, + "@use 'sass:math';\na {\n color: math.sin(1);\n}\n", + "a {\n color: 0.8414709848;\n}\n" +); +grass_test!( + sin_small_negative_degree, + "@use 'sass:math';\na {\n color: math.sin(-1deg);\n}\n", + "a {\n color: -0.0174524064;\n}\n" +); +grass_test!( + sin_small_negative_radian, + "@use 'sass:math';\na {\n color: math.sin(-1rad);\n}\n", + "a {\n color: -0.8414709848;\n}\n" +); +grass_test!( + sin_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.sin(-1);\n}\n", + "a {\n color: -0.8414709848;\n}\n" +); +grass_test!( + sin_pi, + "@use 'sass:math';\na {\n color: math.sin(math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + sin_two_pi, + "@use 'sass:math';\na {\n color: math.sin(2 * math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_error!( + tan_non_angle, + "@use 'sass:math';\na {\n color: math.tan(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +grass_test!( + tan_small_degree, + "@use 'sass:math';\na {\n color: math.tan(1deg);\n}\n", + "a {\n color: 0.0174550649;\n}\n" +); +grass_test!( + tan_small_radian, + "@use 'sass:math';\na {\n color: math.tan(1rad);\n}\n", + "a {\n color: 1.5574077247;\n}\n" +); +grass_test!( + tan_small_no_unit, + "@use 'sass:math';\na {\n color: math.tan(1);\n}\n", + "a {\n color: 1.5574077247;\n}\n" +); +grass_test!( + tan_small_negative_degree, + "@use 'sass:math';\na {\n color: math.tan(-1deg);\n}\n", + "a {\n color: -0.0174550649;\n}\n" +); +grass_test!( + tan_small_negative_radian, + "@use 'sass:math';\na {\n color: math.tan(-1rad);\n}\n", + "a {\n color: -1.5574077247;\n}\n" +); +grass_test!( + tan_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.tan(-1);\n}\n", + "a {\n color: -1.5574077247;\n}\n" +); +grass_test!( + tan_pi, + "@use 'sass:math';\na {\n color: math.tan(math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + tan_two_pi, + "@use 'sass:math';\na {\n color: math.tan(2 * math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + acos_above_one, + "@use 'sass:math';\na {\n color: math.acos(2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + acos_below_negative_one, + "@use 'sass:math';\na {\n color: math.acos(-2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + acos_one, + "@use 'sass:math';\na {\n color: math.acos(1);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + acos_negative_one, + "@use 'sass:math';\na {\n color: math.acos(-1);\n}\n", + "a {\n color: 180deg;\n}\n" +); +grass_test!( + acos_zero, + "@use 'sass:math';\na {\n color: math.acos(0);\n}\n", + "a {\n color: 90deg;\n}\n" +); +grass_test!( + acos_point_five, + "@use 'sass:math';\na {\n color: math.acos(.5);\n}\n", + "a {\n color: 60deg;\n}\n" +); +grass_test!( + acos_nan, + "@use 'sass:math';\na {\n color: math.acos((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + asin_above_one, + "@use 'sass:math';\na {\n color: math.asin(2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + asin_below_negative_one, + "@use 'sass:math';\na {\n color: math.asin(-2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + asin_one, + "@use 'sass:math';\na {\n color: math.asin(1);\n}\n", + "a {\n color: 90deg;\n}\n" +); +grass_test!( + asin_negative_one, + "@use 'sass:math';\na {\n color: math.asin(-1);\n}\n", + "a {\n color: -90deg;\n}\n" +); +grass_test!( + asin_zero, + "@use 'sass:math';\na {\n color: math.asin(0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + asin_point_five, + "@use 'sass:math';\na {\n color: math.asin(.5);\n}\n", + "a {\n color: 30deg;\n}\n" +); +grass_test!( + asin_nan, + "@use 'sass:math';\na {\n color: math.asin((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + atan_above_one, + "@use 'sass:math';\na {\n color: math.atan(2);\n}\n", + "a {\n color: 63.4349488229deg;\n}\n" +); +grass_test!( + atan_below_negative_one, + "@use 'sass:math';\na {\n color: math.atan(-2);\n}\n", + "a {\n color: -63.4349488229deg;\n}\n" +); +grass_test!( + atan_one, + "@use 'sass:math';\na {\n color: math.atan(1);\n}\n", + "a {\n color: 45deg;\n}\n" +); +grass_test!( + atan_negative_one, + "@use 'sass:math';\na {\n color: math.atan(-1);\n}\n", + "a {\n color: -45deg;\n}\n" +); +grass_test!( + atan_zero, + "@use 'sass:math';\na {\n color: math.atan(0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + atan_point_five, + "@use 'sass:math';\na {\n color: math.atan(.5);\n}\n", + "a {\n color: 26.5650511771deg;\n}\n" +); +grass_test!( + atan_nan, + "@use 'sass:math';\na {\n color: math.atan((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + log_above_one, + "@use 'sass:math';\na {\n color: math.log(2);\n}\n", + "a {\n color: 0.6931471806;\n}\n" +); +grass_test!( + log_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(-2);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + log_one, + "@use 'sass:math';\na {\n color: math.log(1);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + log_negative_one, + "@use 'sass:math';\na {\n color: math.log(-1);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + #[ignore = "we do not support Infinity"] + log_zero, + "@use 'sass:math';\na {\n color: math.log(0);\n}\n", + "a {\n color: -Infinity;\n}\n" +); +grass_test!( + log_point_five, + "@use 'sass:math';\na {\n color: math.log(.5);\n}\n", + "a {\n color: -0.6931471806;\n}\n" +); +grass_test!( + log_nan, + "@use 'sass:math';\na {\n color: math.log((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + log_base_nan, + "@use 'sass:math';\na {\n color: math.log(1, (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + log_base_above_one, + "@use 'sass:math';\na {\n color: math.log(2, 2);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + log_base_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -2);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + #[ignore = "we do not support Infinity"] + log_base_one, + "@use 'sass:math';\na {\n color: math.log(2, 1);\n}\n", + "a {\n color: Infinity;\n}\n" +); +grass_test!( + log_base_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -1);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + log_base_zero, + "@use 'sass:math';\na {\n color: math.log(2, 0);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + log_base_point_five, + "@use 'sass:math';\na {\n color: math.log(2, .5);\n}\n", + "a {\n color: -1;\n}\n" +); + +grass_test!( + pow_exponent_and_base_one, + "@use 'sass:math';\na {\n color: math.pow(1, 1);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + pow_exponent_and_base_ten, + "@use 'sass:math';\na {\n color: math.pow(10, 10);\n}\n", + "a {\n color: 10000000000;\n}\n" +); +grass_test!( + pow_base_negative_exponent_positive, + "@use 'sass:math';\na {\n color: math.pow(-2, 3);\n}\n", + "a {\n color: -8;\n}\n" +); +grass_test!( + pow_base_positive_exponent_negative, + "@use 'sass:math';\na {\n color: math.pow(2, -3);\n}\n", + "a {\n color: 0.125;\n}\n" +); +grass_test!( + pow_base_negative_exponent_negative, + "@use 'sass:math';\na {\n color: math.pow(-2, -3);\n}\n", + "a {\n color: -0.125;\n}\n" +); +grass_test!( + pow_base_decimal, + "@use 'sass:math';\na {\n color: math.pow(2.4, 3);\n}\n", + "a {\n color: 13.824;\n}\n" +); +grass_test!( + pow_exponent_decimal, + "@use 'sass:math';\na {\n color: math.pow(2, 3.5);\n}\n", + "a {\n color: 11.313708499;\n}\n" +); +grass_test!( + pow_base_nan, + "@use 'sass:math';\na {\n color: math.pow((0 / 0), 3);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + pow_exponent_nan, + "@use 'sass:math';\na {\n color: math.pow(2, (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + pow_base_and_exponent_nan, + "@use 'sass:math';\na {\n color: math.pow((0 / 0), (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + pow_exponent_zero, + "@use 'sass:math';\na {\n color: math.pow(2, 0);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + hypot_all_same_unit, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px);\n}\n", + "a {\n color: 7.4161984871px;\n}\n" +); +grass_test!( + hypot_negative, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px, -20px);\n}\n", + "a {\n color: 21.3307290077px;\n}\n" +); +grass_test!( + hypot_all_different_but_comparable_unit, + "@use 'sass:math';\na {\n color: math.hypot(1in, 2cm, 3mm, 4pt, 5pc);\n}\n", + "a {\n color: 1.5269191636in;\n}\n" +); +grass_test!( + hypot_all_no_unit, + "@use 'sass:math';\na {\n color: math.hypot(1, 2, 3);\n}\n", + "a {\n color: 3.7416573868;\n}\n" +); +grass_test!( + hypot_nan_has_comparable_unit, + "@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, math.acos(2));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_error!( + hypot_no_args, + "@use 'sass:math';\na {\n color: math.hypot();\n}\n", + "Error: At least one argument must be passed." +); +grass_error!( + hypot_first_has_no_unit_third_has_unit, + "@use 'sass:math';\na {\n color: math.hypot(1, 2, 3px);\n}\n", + "Error: Argument 1 is unitless but argument 3 has unit px. Arguments must all have units or \ + all be unitless." +); +grass_error!( + hypot_non_numeric_argument, + "@use 'sass:math';\na {\n color: math.hypot(1, red, 3);\n}\n", + "Error: red is not a number." +); +grass_error!( + hypot_units_not_comparable, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2in, 3rem);\n}\n", + "Error: Incompatible units px and rem." +); +grass_error!( + hypot_nan_has_no_unit_but_first_has_unit, + "@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, (0 / 0));\n}\n", + "Error: Argument 1 has unit deg but argument 3 is unitless. Arguments must all have units or \ + all be unitless." +); +grass_test!( + atan2_both_positive, + "@use 'sass:math';\na {\n color: math.atan2(3, 4);\n}\n", + "a {\n color: 36.8698976458deg;\n}\n" +); +grass_test!( + atan2_first_negative, + "@use 'sass:math';\na {\n color: math.atan2(-3, 4);\n}\n", + "a {\n color: -36.8698976458deg;\n}\n" +); +grass_test!( + atan2_second_negative, + "@use 'sass:math';\na {\n color: math.atan2(3, -4);\n}\n", + "a {\n color: 143.1301023542deg;\n}\n" +); +grass_test!( + atan2_both_negative, + "@use 'sass:math';\na {\n color: math.atan2(-3, -4);\n}\n", + "a {\n color: -143.1301023542deg;\n}\n" +); +grass_test!( + atan2_first_positive_second_zero, + "@use 'sass:math';\na {\n color: math.atan2(3, 0);\n}\n", + "a {\n color: 90deg;\n}\n" +); +grass_test!( + atan2_first_negative_second_zero, + "@use 'sass:math';\na {\n color: math.atan2(-3, 0);\n}\n", + "a {\n color: -90deg;\n}\n" +); +grass_test!( + atan2_first_zero_second_positive, + "@use 'sass:math';\na {\n color: math.atan2(0, 4);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + atan2_first_zero_second_negative, + "@use 'sass:math';\na {\n color: math.atan2(0, -4);\n}\n", + "a {\n color: 180deg;\n}\n" +); +grass_test!( + atan2_both_zero, + "@use 'sass:math';\na {\n color: math.atan2(0, 0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +grass_test!( + atan2_both_same_unit, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4px);\n}\n", + "a {\n color: 36.8698976458deg;\n}\n" +); +grass_test!( + atan2_both_different_but_comparable_unit, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4in);\n}\n", + "a {\n color: 0.4476141709deg;\n}\n" +); +grass_error!( + atan2_first_unitless_second_unit, + "@use 'sass:math';\na {\n color: math.atan2(3, 4rem);\n}\n", + "Error: $y is unitless but $x has unit rem. Arguments must all have units or all be unitless." +); +grass_error!( + atan2_first_unit_second_unitless, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4);\n}\n", + "Error: $y has unit px but $x is unitless. Arguments must all have units or all be unitless." +); +grass_error!( + atan2_incompatible_units, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4rem);\n}\n", + "Error: Incompatible units px and rem." +); +grass_error!( + atan2_nan_incompatible_units, + "@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3);\n}\n", + "Error: $y has unit deg but $x is unitless. Arguments must all have units or all be unitless." +); +grass_test!( + atan2_first_nan, + "@use 'sass:math';\na {\n color: math.atan2((0/0), 0);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + atan2_second_nan, + "@use 'sass:math';\na {\n color: math.atan2(0, (0/0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + atan2_both_nan, + "@use 'sass:math';\na {\n color: math.atan2((0/0), (0/0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + atan2_nan_with_same_units, + "@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3deg);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); diff --git a/css/parser/tests/grass_media.rs b/css/parser/tests/grass_media.rs new file mode 100644 index 000000000000..f5024739161e --- /dev/null +++ b/css/parser/tests/grass_media.rs @@ -0,0 +1,121 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + basic_toplevel, + "@media foo {\n a {\n color: red;\n }\n}\n" +); +grass_error!( + no_params, + "@media {\n a {\n color: red;\n }\n}\n", + "Error: Expected identifier." +); +grass_test!( + basic_nested, + "a {\n @media foo {\n color: red;\n }\n}\n", + "@media foo {\n a {\n color: red;\n }\n}\n" +); +grass_test!(empty_body, "@media (min-width: 2px) {}", ""); +grass_test!( + newlines_are_not_emitted_for_child_styles, + "a { + @media screen { + b { + color: red; + } + c { + color: green; + } + } + }", + "@media screen {\n a b {\n color: red;\n }\n a c {\n color: green;\n }\n}\n" +); +grass_test!( + multiple_identifiers_in_query, + "@media not screen { + a { + color: red; + } + }", + "@media not screen {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + multiple_identifiers_in_query_second_is_and, + "@media print and (foo: 1 2 3) { + a { + color: red; + } + }", + "@media print and (foo: 1 2 3) {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + single_identifier_inside_parens, + "@media (color) {a {color: red;}}", + "@media (color) {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + quoted_colon_in_parens, + "@media screen and (\":\") { + a { + color: red; + } + }", + "@media screen and (:) {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + multiline_comments_everywhere, + "@media/**/foo/**/and/**/(/**/bar/**/)/**/{ + a { + color: red; + } + }", + "@media foo and (bar) {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + comparison_in_query, + "@media (100px < 400px) { + a { + interpolation: in-parens + } + }", + "@media (100px < 400px) {\n a {\n interpolation: in-parens;\n }\n}\n" +); +grass_test!( + interpolated_comparison_in_query, + "@media (#{100px < 400px}) { + a { + interpolation: in-parens + } + }", + "@media (true) {\n a {\n interpolation: in-parens;\n }\n}\n" +); +grass_test!( + single_eq_in_query, + "@media (height=600px) { + a { + b: c + } + } + ", + "@media (height = 600px) {\n a {\n b: c;\n }\n}\n" +); +grass_test!( + double_eq_in_query, + "@media (height==600px) { + a { + b: c + } + } + ", + "@media (false) {\n a {\n b: c;\n }\n}\n" +); +grass_error!( + media_feature_missing_closing_paren, + "@media foo and (bar:a", + "Error: expected \")\"." +); +grass_error!( + media_feature_missing_curly_brace_after_hash, + "@media foo and # {}", + "Error: expected \"{\"." +); diff --git a/css/parser/tests/grass_meta.rs b/css/parser/tests/grass_meta.rs new file mode 100644 index 000000000000..860963fbe458 --- /dev/null +++ b/css/parser/tests/grass_meta.rs @@ -0,0 +1,300 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + if_true, + "a {\n color: if(true, 1, 2)\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + if_equal, + "a {\n color: if(1 == 1, 1, 2)\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + if_not_equal, + "a {\n color: if(1 != 1, 1, 2)\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + if_named_args, + "a {\n color: if($condition: true, $if-true: 1, $if-false: 2)\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + if_false, + "a {\n color: if(false, 1, 2);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + feature_exists_dbl_quoted, + "a {\n color: feature-exists(\"at-error\")\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_sgl_quoted, + "a {\n color: feature-exists('at-error')\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_no_quotes, + "a {\n color: feature-exists(at-error)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_at_error, + "a {\n color: feature-exists(at-error)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_named_arg, + "a {\n color: feature-exists($feature: at-error)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_case_sensitive, + "a {\n color: feature-exists(at-erroR)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + feature_exists_global_variable_shadowing, + "a {\n color: feature-exists(global-variable-shadowing)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_extend_selector_pseudoclass, + "a {\n color: feature-exists(extend-selector-pseudoclass)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_units_level_3, + "a {\n color: feature-exists(units-level-3)\n}\n", + "a {\n color: true;\n}\n" +); +// Unignore as more features are added +grass_test!( + #[ignore] + feature_exists_custom_property, + "a {\n color: feature-exists(custom-property)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + feature_exists_nonsense, + "a {\n color: feature-exists(foo)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + feature_exists_at_error_named_arg, + "a {\n color: feature-exists($feature: at-error)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + unit_px, + "a {\n color: unit(1px)\n}\n", + "a {\n color: \"px\";\n}\n" +); +grass_test!( + unit_none, + "a {\n color: unit(1)\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + unit_named_args, + "a {\n color: unit($number: 1px)\n}\n", + "a {\n color: \"px\";\n}\n" +); +grass_test!( + type_of_number, + "a {\n color: type-of(1)\n}\n", + "a {\n color: number;\n}\n" +); +grass_test!( + type_of_number_unit, + "a {\n color: type-of(1px)\n}\n", + "a {\n color: number;\n}\n" +); +grass_test!( + type_of_unquoted, + "a {\n color: type-of(foo)\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_sgl_unquoted, + "a {\n color: type-of('red')\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_dbl_unquoted, + "a {\n color: type-of(\"red\")\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_3_hex_color, + "a {\n color: type-of(#fff)\n}\n", + "a {\n color: color;\n}\n" +); +grass_test!( + type_of_6_hex_color, + "a {\n color: type-of(#ffffff)\n}\n", + "a {\n color: color;\n}\n" +); +grass_test!( + type_of_named_color, + "a {\n color: type-of(red)\n}\n", + "a {\n color: color;\n}\n" +); +grass_test!( + type_of_empty_list, + "a {\n color: type-of(())\n}\n", + "a {\n color: list;\n}\n" +); +grass_test!( + type_of_spaced_list, + "a {\n color: type-of(1 2 3)\n}\n", + "a {\n color: list;\n}\n" +); +grass_test!( + type_of_important, + "a {\n color: type-of(!important)\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_true, + "a {\n color: type-of(true)\n}\n", + "a {\n color: bool;\n}\n" +); +grass_test!( + type_of_false, + "a {\n color: type-of(false)\n}\n", + "a {\n color: bool;\n}\n" +); +grass_test!( + type_of_null, + "a {\n color: type-of(null)\n}\n", + "a {\n color: null;\n}\n" +); +grass_test!( + type_of_ident_plus_ident, + "a {\n color: type-of(hi + bye)\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_map, + "a {\n color: type-of((a: b, c: d))\n}\n", + "a {\n color: map;\n}\n" +); +grass_test!( + type_of_parens, + "a {\n color: type-of(((a)))\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + type_of_unary_op, + "a {\n color: type-of(- 2)\n}\n", + "a {\n color: number;\n}\n" +); +grass_test!( + type_of_nan, + "a {\n color: type-of((0 / 0))\n}\n", + "a {\n color: number;\n}\n" +); +grass_test!( + type_of_arglist, + "@mixin foo($a...) {color: type-of($a);}\na {@include foo(1, 2, 3, 4, 5);}", + "a {\n color: arglist;\n}\n" +); +grass_test!( + unitless_px, + "a {\n color: unitless(1px)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + unitless_num, + "a {\n color: unitless(1)\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + unitless_string, + "a {\n color: unitless(foo)\n}\n", + "Error: $number: foo is not a number." +); +grass_test!( + variable_does_exist, + "$a: red; a {\n color: variable-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + variable_does_not_exist, + "a {\n color: variable-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + variable_exists_named, + "$a: red; a {\n color: variable-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + variable_exists_quoted, + "$a: red; a {\n color: variable-exists('a')\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + variable_exists_local_is_null, + "a {\n $a: null; color: variable-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + variable_exists_global_is_null, + "$a: null; a {\n color: variable-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + variable_exists_not_string, + "a {\n color: variable-exists(12px)\n}\n", + "Error: $name: 12px is not a string." +); +grass_test!( + mixin_does_exist, + "@mixin a{} a {\n color: mixin-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + mixin_does_not_exist, + "a {\n color: mixin-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + mixin_exists_named, + "@mixin a{} a {\n color: mixin-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + function_does_exist, + "@function a(){} a {\n color: function-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + builtin_function_does_exist, + "a {\n color: function-exists(function-exists)\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + function_does_not_exist, + "a {\n color: function-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + function_exists_named, + "@function a(){} a {\n color: function-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + function_exists_non_string, + "a {color: function-exists(12px)}", + "Error: $name: 12px is not a string." +); +grass_error!( + mixin_exists_non_string, + "a {color: mixin-exists(12px)}", + "Error: $name: 12px is not a string." +); diff --git a/css/parser/tests/grass_meta_module.rs b/css/parser/tests/grass_meta_module.rs new file mode 100644 index 000000000000..7fcce113db9b --- /dev/null +++ b/css/parser/tests/grass_meta_module.rs @@ -0,0 +1,37 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + module_functions_builtin, + "@use 'sass:meta';\na {\n color: inspect(meta.module-functions(meta));\n}\n", + "a {\n color: (\"feature-exists\": get-function(\"feature-exists\"), \"inspect\": \ + get-function(\"inspect\"), \"type-of\": get-function(\"type-of\"), \"keywords\": \ + get-function(\"keywords\"), \"global-variable-exists\": \ + get-function(\"global-variable-exists\"), \"variable-exists\": \ + get-function(\"variable-exists\"), \"function-exists\": get-function(\"function-exists\"), \ + \"mixin-exists\": get-function(\"mixin-exists\"), \"content-exists\": \ + get-function(\"content-exists\"), \"module-variables\": get-function(\"module-variables\"), \ + \"module-functions\": get-function(\"module-functions\"), \"get-function\": \ + get-function(\"get-function\"), \"call\": get-function(\"call\"));\n}\n" +); +grass_test!( + module_variables_builtin, + "@use 'sass:meta';\n@use 'sass:math';\na {\n color: \ + inspect(meta.module-variables(math));\n}\n", + "a {\n color: (\"e\": 2.7182818285, \"pi\": 3.1415926536);\n}\n" +); +grass_test!( + global_var_exists_module, + "@use 'sass:math';\na {\n color: global-variable-exists(pi, $module: math);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + fn_exists_builtin, + "@use 'sass:math';\na {\n color: function-exists(acos, $module: math);\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + fn_exists_module_dne, + "a {\n color: function-exists(c, d);\n}\n", + "Error: There is no module with the namespace \"d\"." +); diff --git a/css/parser/tests/grass_min_max.rs b/css/parser/tests/grass_min_max.rs new file mode 100644 index 000000000000..9d2187272438 --- /dev/null +++ b/css/parser/tests/grass_min_max.rs @@ -0,0 +1,113 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + min_not_evaluated_units_percent, + "a {\n color: min(1%, 2%);\n}\n", + "a {\n color: min(1%, 2%);\n}\n" +); +grass_test!( + min_not_evaluated_units_px, + "a {\n color: min(1px, 2px);\n}\n", + "a {\n color: min(1px, 2px);\n}\n" +); +grass_test!( + min_not_evaluated_no_units, + "a {\n color: min(1, 2);\n}\n", + "a {\n color: min(1, 2);\n}\n" +); +grass_test!( + min_not_evaluated_incompatible_units, + "a {\n color: min(1%, 2vh);\n}\n", + "a {\n color: min(1%, 2vh);\n}\n" +); +grass_test!( + min_not_evaluated_interpolation, + "$a: 1%;\n$b: 2%;\na {\n color: min(#{$a}, #{$b});;\n}\n", + "a {\n color: min(1%, 2%);\n}\n" +); +grass_test!( + min_evaluated_variable_units_percent, + "$a: 1%;\n$b: 2%;\na {\n color: min($a, $b);\n}\n", + "a {\n color: 1%;\n}\n" +); +grass_test!( + min_evaluated_variable_units_px, + "$a: 1px;\n$b: 2px;\na {\n color: min($a, $b);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_error!( + min_arg_of_incorrect_type, + "$a: 1px;\n$b: 2px;\na {\n color: min($a, $b, foo);\n}\n", + "Error: foo is not a number." +); +grass_error!( + min_too_few_args, + "a {\n color: min();\n}\n", + "Error: At least one argument must be passed." +); +// note: we explicitly have units in the opposite order of `dart-sass`. +// see https://github.com/sass/dart-sass/issues/766 +grass_error!( + min_incompatible_units, + "$a: 1px;\n$b: 2%;\na {\n color: min($a, $b);\n}\n", + "Error: Incompatible units px and %." +); +grass_test!( + max_not_evaluated_units_percent, + "a {\n color: max(1%, 2%);\n}\n", + "a {\n color: max(1%, 2%);\n}\n" +); +grass_test!( + max_not_evaluated_units_px, + "a {\n color: max(1px, 2px);\n}\n", + "a {\n color: max(1px, 2px);\n}\n" +); +grass_test!( + max_not_evaluated_no_units, + "a {\n color: max(1, 2);\n}\n", + "a {\n color: max(1, 2);\n}\n" +); +grass_test!( + max_not_evaluated_incompatible_units, + "a {\n color: max(1%, 2vh);\n}\n", + "a {\n color: max(1%, 2vh);\n}\n" +); +grass_test!( + max_not_evaluated_interpolation, + "$a: 1%;\n$b: 2%;\na {\n color: max(#{$a}, #{$b});;\n}\n", + "a {\n color: max(1%, 2%);\n}\n" +); +grass_test!( + max_evaluated_variable_units_percent, + "$a: 1%;\n$b: 2%;\na {\n color: max($a, $b);\n}\n", + "a {\n color: 2%;\n}\n" +); +grass_test!( + max_evaluated_variable_units_px, + "$a: 1px;\n$b: 2px;\na {\n color: max($a, $b);\n}\n", + "a {\n color: 2px;\n}\n" +); +grass_test!( + max_evaluated_binop, + "a {\n color: max(100% - lightness(red) - 2%);\n}\n", + "a {\n color: 48%;\n}\n" +); +grass_error!( + max_arg_of_incorrect_type, + "$a: 1px;\n$b: 2px;\na {\n color: max($a, $b, foo);\n}\n", + "Error: foo is not a number." +); +grass_error!( + max_too_few_args, + "a {\n color: max();\n}\n", + "Error: At least one argument must be passed." +); +// note: we explicitly have units in the opposite order of `dart-sass`. +// see https://github.com/sass/dart-sass/issues/766 +grass_error!( + max_incompatible_units, + "$a: 1px;\n$b: 2%;\na {\n color: max($a, $b);\n}\n", + "Error: Incompatible units px and %." +); +// todo: special functions, min(calc(1), $b); diff --git a/css/parser/tests/grass_misc.rs b/css/parser/tests/grass_misc.rs new file mode 100644 index 000000000000..c82f464584e0 --- /dev/null +++ b/css/parser/tests/grass_misc.rs @@ -0,0 +1,103 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + ident_starts_with_hyphen, + "a {\n foo: -webkit-bar-baz;\n}\n" +); +grass_test!( + ident_starts_with_double_hyphen, + "a {\n foo: --webkit-bar-baz;\n}\n" +); +grass_test!(ident_with_num, "el1 {\n a: b;\n}\n"); +grass_test!( + emits_double_newline_between_unrelated_styles, + "a {\n color: red;\n}\n\nb {\n color: blue;\n}\n" +); +grass_test!( + variable_interchangable_hypen_dash, + "$a-b: red; $a_b: green; a {\n color: $a-b;\n}\n", + "a {\n color: green;\n}\n" +); +grass_test!( + two_semicolons, + "a {\n color: red;;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + five_semicolons, + "a {\n color: red;;;;;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + two_semicolons_whitespace, + "a {\n color: red; ;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + utf8_ident_before_len, + "a {\n color: length(😀red);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + utf8_ident_before, + "a {\n color: 😀red;\n}\n", + "@charset \"UTF-8\";\na {\n color: 😀red;\n}\n" +); +grass_test!( + utf8_ident_after_len, + "a {\n color: length(red😁)\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + utf8_ident_after, + "a {\n color: red😁\n}\n", + "@charset \"UTF-8\";\na {\n color: red😁;\n}\n" +); +grass_test!( + no_space_before_style, + "a {\n color:red\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + does_not_combine_idents_with_leading_hyphen, + "a {\n color: a -b;\n}\n" +); +grass_test!( + does_not_combine_idents_with_leading_hyphen_list, + "a {\n color: a -b c;\n}\n" +); +grass_test!( + does_not_combine_idents_with_leading_hyphen_all, + "a {\n color: -a -b -c;\n}\n" +); +grass_test!( + args_handles_arbitrary_number_of_parens, + "a {\n color: inspect((((((a))))));\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + no_space_between_colon_and_style_variable, + "$base-color: #036;\na {\n color:lighten($base-color, 5%);\n}", + "a {\n color: #004080;\n}\n" +); +grass_test!( + semicolon_after_closing_brace, + "a {\n color: foo;\n};", + "a {\n color: foo;\n}\n" +); +grass_test!( + builtin_functions_interchangeable_underscore_hyphen, + "a {\n color: ie_hex-str(rgba(0, 255, 0, 0.5));\n}\n", + "a {\n color: #8000FF00;\n}\n" +); +grass_test!( + empty_style_after_style_emits_one_newline, + "a {\n a: b\n}\n\nb {}\n", + "a {\n a: b;\n}\n" +); +grass_test!( + file_begins_with_utf8_bom, + "\u{feff}a {\n color: red\n}\n", + "a {\n color: red;\n}\n" +); diff --git a/css/parser/tests/grass_mixins.rs b/css/parser/tests/grass_mixins.rs new file mode 100644 index 000000000000..fe898f5e932f --- /dev/null +++ b/css/parser/tests/grass_mixins.rs @@ -0,0 +1,614 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + basic_mixin, + "@mixin a {\n color: red;\n}\n\nb {\n @include a;\n}\n", + "b {\n color: red;\n}\n" +); +grass_test!(empty_mixin, "@mixin a {}\n\nb {\n @include a;\n}\n", ""); +grass_test!( + just_a_comment, + "@mixin foo() {\n /* begin foo */\n}\n\na {\n @include foo();\n}\n", + "a {\n /* begin foo */\n}\n" +); +grass_test!( + mixin_two_styles, + "@mixin a {\n color: red;\n color: blue;\n}\n\nb {\n @include a;\n}\n", + "b {\n color: red;\n color: blue;\n}\n" +); +grass_test!( + mixin_ruleset, + "@mixin a {\n b {\n color: red;\n }\n}\nb {\n @include a;\n}\n", + "b b {\n color: red;\n}\n" +); +grass_test!( + mixin_two_rulesets, + "@mixin a {\n b {\n color: red;\n }\n c {\n color: blue;\n }\n}\nd {\n @include \ + a;\n}\n", + "d b {\n color: red;\n}\nd c {\n color: blue;\n}\n" +); +grass_test!( + mixin_ruleset_and_style, + "@mixin a {\n b {\n color: red;\n }\n color: blue;\n}\nd {\n @include a;\n}\n", + "d {\n color: blue;\n}\nd b {\n color: red;\n}\n" +); +grass_test!( + mixin_style_and_ruleset, + "@mixin a {\n color: blue;\n b {\n color: red;\n}\n}\nd {\n @include a;\n}\n", + "d {\n color: blue;\n}\nd b {\n color: red;\n}\n" +); +grass_test!( + mixin_nested_rulesets, + "@mixin a {\n b {\n c {\n color: red;\n}\n}\n}\nd {\n @include a;\n}\n", + "d b c {\n color: red;\n}\n" +); +grass_test!( + mixin_removes_empty_ruleset, + "@mixin a {\n color: red; b {\n}\n}\nd {\n @include a;\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_variable_scope_one_ruleset, + "@mixin a {\n $a: blue;\nb {\n $a: red;\n} color: $a\n}\nd {\n @include a;\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_no_args, + "@mixin a {\n color: red;\n}\nd {\n @include a();\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_single_arg, + "@mixin a($b) {\n color: $b;\n}\nd {\n @include a(red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_two_args, + "@mixin a($b, $c) {\n color: $b;\n color: $c\n}\nd {\n @include a(red, blue);\n}\n", + "d {\n color: red;\n color: blue;\n}\n" +); +grass_test!( + mixin_arg_trailing_comma, + "@mixin a($b, $c,) {\n color: $b;\n color: $c\n}\nd {\n @include a(red, blue);\n}\n", + "d {\n color: red;\n color: blue;\n}\n" +); +grass_test!( + mixin_property_interpolation, + "@mixin a($b) {\n #{$b}: red;\n}\nd {\n @include a(color);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_style_interpolation, + "@mixin a($b) {\n color: #{$b};\n}\nd {\n @include a(red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_simple_default_value, + "@mixin a($b: red) {\n color: $b;\n}\nd {\n @include a;\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + mixin_second_value_default, + "@mixin a($a, $b: blue) {\n color: $a $b;\n}\nd {\n @include a(red);\n}\n", + "d {\n color: red blue;\n}\n" +); +grass_test!( + mixin_two_default_values, + "@mixin a($a: red, $b: blue) {\n color: $a $b;\n}\nd {\n @include a;\n}\n", + "d {\n color: red blue;\n}\n" +); +grass_test!( + mixin_override_default_value_positionally, + "@mixin a($a: red) {\n color: $a;\n}\nd {\n @include a(blue);\n}\n", + "d {\n color: blue;\n}\n" +); +grass_test!( + mixin_keyword_arg, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a($a: blue);\n}\n", + "d {\n color: blue;\n}\n" +); +grass_test!( + mixin_keyword_arg_override_default, + "@mixin a($a: red) {\n color: $a;\n}\nd {\n @include a($a: blue);\n}\n", + "d {\n color: blue;\n}\n" +); +grass_test!( + mixin_keyword_applies_to_second_arg, + "@mixin a($a: red, $b) {\n color: $a $b;\n}\nd {\n @include a($b: blue);\n}\n", + "d {\n color: red blue;\n}\n" +); +grass_test!( + mixin_two_keywords, + "@mixin a($a, $b) {\n color: $a $b;\n}\nd {\n @include a($a: red, $b: blue);\n}\n", + "d {\n color: red blue;\n}\n" +); +grass_test!( + mixin_two_keywords_wrong_direction, + "@mixin a($a, $b) {\n color: $a $b;\n}\nd {\n @include a($b: blue, $a: red);\n}\n", + "d {\n color: red blue;\n}\n" +); +grass_test!( + variable_in_call_args, + "@mixin a($a) {\n color: $a;\n}\nd {\n $c: red;\n @include a($c);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_before_positional_call_arg, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a(/*foo*/red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_after_positional_call_arg, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a(red/*foo*/);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_before_keyword_call_arg_val, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a($a: /*foo*/red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_after_keyword_call_arg_val, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a($a: red/*foo*/);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_before_keyword_call_arg_name, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a(/*foo*/$a: red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + comment_after_keyword_call_arg_name, + "@mixin a($a) {\n color: $a;\n}\nd {\n @include a($a/*foo*/: red);\n}\n", + "d {\n color: red;\n}\n" +); +grass_test!( + toplevel_include, + "@mixin a {\n a {\n color: red;\n }\n}\n\n@include a;\n", + "a {\n color: red;\n}\n" +); +grass_test!( + include_list, + "@mixin foo($x) {\n color: $x;\n}\na {\n @include foo(0px 0px 0px 0px #ef8086 inset \ + !important);\n}\n", + "a {\n color: 0px 0px 0px 0px #ef8086 inset !important;\n}\n" +); +grass_test!( + content_without_variable, + "@mixin foo {\n @content;\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + content_with_variable, + "@mixin foo($a) {\n @content;\n}\n\na {\n @include foo(red) {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + mixin_style_does_not_end_with_semicolon, + "@mixin foo {\n color: red\n}\n\na {\n @include foo;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + args_hyphen_arg_allows_underscore, + "@mixin foo($a-b) {\n color: $a-b;\n color: $a_b;\n}\na {\n @include foo($a_b: a);\n \ + @include foo($a-b: a);\n}\n", + "a {\n color: a;\n color: a;\n color: a;\n color: a;\n}\n" +); +grass_test!( + args_underscore_arg_allows_hyphen, + "@mixin foo($a_b) {\n color: $a-b;\n color: $a_b;\n}\na {\n @include foo($a_b: a);\n \ + @include foo($a-b: a);\n}\n", + "a {\n color: a;\n color: a;\n color: a;\n color: a;\n}\n" +); +grass_test!( + control_flow_in_content, + "@mixin foo {\n @content;\n}\n\na {\n @include foo {@if true {color: red;}}\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + content_in_control_flow, + "@mixin foo() {\n @if true {\n @content;\n }\n}\n\na {\n @include foo {\n color: red;\n }\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + content_inside_unknown_at_rule, + "@mixin foo() {\n @foo (max-width: max) {\n @content;\n }\n}\n\na {\n \ + @include foo {\n color: red;\n }\n}\n", + "@foo (max-width: max) {\n a {\n color: red;\n }\n}\n" +); +grass_test!( + content_inside_media, + "@mixin foo() {\n @media (max-width: max) {\n @content;\n }\n}\n\na {\n \ + @include foo {\n color: red;\n }\n}\n", + "@media (max-width: max) {\n a {\n color: red;\n }\n}\n" +); +grass_error!( + function_inside_mixin, + "@mixin foo() {\n @function bar() {\n @return foo;\n }\n}\n\na {\n @include \ + foo {\n color: red;\n }\n}\n", + "Error: Mixins may not contain function declarations." +); +grass_error!( + content_inside_control_flow_outside_mixin, + "a {\n @if true {\n @content;\n }\n}\n", + "Error: @content is only allowed within mixin declarations." +); +grass_error!( + undefined_mixin, + "a {@include foo;}", + "Error: Undefined mixin." +); +grass_error!( + body_missing_closing_curly_brace, + "@mixin foo() {", + "Error: expected \"}\"." +); +grass_test!( + include_empty_args_no_semicolon, + "@mixin c {}\n\na {\n @include c()\n}\n", + "" +); +grass_test!( + local_variable_declared_before_mixin_is_still_in_scope, + "@mixin foo {}\n\na {\n $a: red;\n @include foo;\n color: $a;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + empty_content_args, + "@mixin foo { + @content() + } + + a { + @include foo { + color: red; + }; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + empty_content_args_using_empty_args, + "@mixin foo { + @content() + } + + a { + @include foo using () { + color: red; + }; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_using_one_arg, + "@mixin foo { + @content(red) + } + + a { + @include foo using ($a) { + color: $a; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + multiple_content_using_different_args, + "@mixin foo { + @content(1); + @content(2); + } + + @mixin bar { + @include foo using ($a) { + color: $a + } + } + + a { + @include bar; + }", + "a {\n color: 1;\n color: 2;\n}\n" +); +grass_test!( + chained_content, + "@mixin foo { + @content; + } + + @mixin bar { + @include foo { + @content; + } + } + + a { + @include bar { + color: red; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_can_access_local_variables, + "@mixin foo { + @content; + } + + a { + $bar: red; + + @include foo { + color: $bar; + } + }", + "a {\n color: red;\n}\n" +); +grass_error!( + content_using_too_many_args, + "@mixin foo { + @content(red, blue) + } + + a { + @include foo using ($a) { + color: $a; + } + }", + "Error: Only 1 argument allowed, but 2 were passed." +); +grass_error!( + content_using_too_few_args, + "@mixin foo { + @content() + } + + a { + @include foo using ($a) { + color: $a; + } + }", + "Error: Missing argument $a." +); +grass_test!( + inner_mixin_can_modify_scope, + "a { + $a: red; + @mixin foo { + color: $a; + } + $a: green; + @include foo; + }", + "a {\n color: green;\n}\n" +); +grass_test!( + redeclaration_in_inner_scope, + "@mixin foo { + color: foo; + } + + a { + @include foo(); + + @mixin foo { + color: bar; + } + + a { + @mixin foo { + color: baz; + } + } + + @include foo(); + }", + "a {\n color: foo;\n color: bar;\n}\n" +); +grass_test!( + three_depth_of_content, + "@mixin foo($arg) { + @include bar { + color: $arg; + } + } + + @mixin bar { + @include baz { + @content; + } + } + + @mixin baz { + @content; + } + + @mixin font-size($value) { + @include foo($value); + } + + a { + @include font-size(1rem); + }", + "a {\n color: 1rem;\n}\n" +); +grass_test!( + can_access_global_variables_set_after_other_include, + "$x: true; + + @mixin foobar() { + @if $x { + $x: false !global; + color: foo; + } + + @else { + $x: true !global; + color: bar; + } + } + + a { + @include foobar(); + $x: true !global; + @include foobar(); + }", + "a {\n color: foo;\n color: foo;\n}\n" +); +grass_test!( + can_access_variables_declared_before_content, + "@mixin foo { + $a: red; + + @content; + + color: $a; + } + + a { + @include foo; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_contains_variable_declared_in_outer_scope_not_declared_at_root, + "a { + $a: red; + + @mixin foo { + @content; + } + + @include foo { + color: $a; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_contains_variable_declared_in_outer_scope_declared_at_root, + "@mixin foo { + @content; + } + + a { + $a: red; + + @include foo { + color: $a; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_contains_variable_declared_in_outer_scope_not_declared_at_root_and_modified, + "a { + $a: red; + + @mixin foo { + $a: green; + @content; + } + + @include foo { + color: $a; + } + }", + "a {\n color: green;\n}\n" +); +grass_test!( + content_contains_variable_declared_in_outer_scope_declared_at_root_and_modified, + "@mixin foo { + $a: green; + @content; + } + + a { + $a: red; + + + @include foo { + color: $a; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + content_default_arg_value_no_parens, + "a { + @mixin foo { + @content; + } + + @include foo using ($a: red) { + color: $a; + } + }", + "a {\n color: red;\n}\n" +); +grass_test!( + space_between_content_and_args, + "space-after-content { + @mixin mixin { + @content /**/ (value1, value2); + } + + @include mixin using ($arg1, $arg2) { + arg1: $arg1; + arg2: $arg2; + } + }", + "space-after-content {\n arg1: value1;\n arg2: value2;\n}\n" +); +grass_test!( + space_between_mixin_and_args, + "@mixin foo /**/ () /**/ { + color: red; + } + + a { + @include foo; + }", + "a {\n color: red;\n}\n" +); +grass_error!( + mixin_in_function, + "@function foo() { + @mixin bar {} + } + a { + color: foo(); + } + ", + "Error: This at-rule is not allowed here." +); +grass_error!( + mixin_in_mixin, + "@mixin foo { + @mixin bar {} + } + a { + @include foo; + } + ", + "Error: Mixins may not contain mixin declarations." +); +grass_error!( + mixin_in_control_directives, + "@if true { + @mixin bar {} + }", + "Error: Mixins may not be declared in control directives." +); +grass_error!( + does_not_allow_interpolation_in_name_of_declaration, + "@mixin n#{a}me { + color: red; + } + + a { + @include name; + }", + "Error: expected \"{\"." +); diff --git a/css/parser/tests/grass_modulo.rs b/css/parser/tests/grass_modulo.rs new file mode 100644 index 000000000000..aab453af7736 --- /dev/null +++ b/css/parser/tests/grass_modulo.rs @@ -0,0 +1,68 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + px_mod_px, + "a {\n color: 10px % 2px;\n}\n", + "a {\n color: 0px;\n}\n" +); +grass_test!( + px_mod_in, + "a {\n color: 10px % 2in;\n}\n", + "a {\n color: 10px;\n}\n" +); +grass_test!( + px_mod_none, + "a {\n color: 10px % 2;\n}\n", + "a {\n color: 0px;\n}\n" +); +grass_test!( + none_mod_px, + "a {\n color: 10 % 2px;\n}\n", + "a {\n color: 0px;\n}\n" +); +grass_test!( + none_mod_none, + "a {\n color: 10 % 2;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + zero_mod_zero, + "a {\n color: 0 % 0;\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + positive_mod_zero, + "a {\n color: 1 % 0;\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + positive_unit_mod_zero, + "a {\n color: 1px % 0;\n}\n", + "a {\n color: NaNpx;\n}\n" +); +grass_test!( + positive_mod_zero_unit, + "a {\n color: 1 % 0px;\n}\n", + "a {\n color: NaNpx;\n}\n" +); +grass_test!( + positive_unit_mod_zero_unit_same, + "a {\n color: 1px % 0px;\n}\n", + "a {\n color: NaNpx;\n}\n" +); +grass_test!( + positive_unit_mod_zero_unit_different_compatible_takes_first_1, + "a {\n color: 1px % 0in;\n}\n", + "a {\n color: NaNpx;\n}\n" +); +grass_test!( + positive_unit_mod_zero_unit_different_compatible_takes_first_2, + "a {\n color: 1in % 0px;\n}\n", + "a {\n color: NaNin;\n}\n" +); +grass_error!( + positive_unit_mod_zero_unit_incompatible_units, + "a {\n color: 1rem % 0px;\n}\n", + "Error: Incompatible units rem and px." +); diff --git a/css/parser/tests/grass_multiplication.rs b/css/parser/tests/grass_multiplication.rs new file mode 100644 index 000000000000..be75973c84c4 --- /dev/null +++ b/css/parser/tests/grass_multiplication.rs @@ -0,0 +1,28 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + map_lhs_mul, + "a {color: (a: b) * 1;}", + "Error: Undefined operation \"(a: b) * 1\"." +); +grass_error!( + map_rhs_mul, + "a {color: 1 * (a: b);}", + "Error: Undefined operation \"1 * (a: b)\"." +); +grass_error!( + function_lhs_mul, + "a {color: get-function(lighten) * 1;}", + "Error: Undefined operation \"get-function(\"lighten\") * 1\"." +); +grass_error!( + function_rhs_mul, + "a {color: 1 * get-function(lighten);}", + "Error: Undefined operation \"1 * get-function(\"lighten\")\"." +); +grass_error!( + null_mul_number, + "a {color: null * 1;}", + "Error: Undefined operation \"null * 1\"." +); diff --git a/css/parser/tests/grass_nan.rs b/css/parser/tests/grass_nan.rs new file mode 100644 index 000000000000..94f42bce2176 --- /dev/null +++ b/css/parser/tests/grass_nan.rs @@ -0,0 +1,188 @@ +#[macro_use] +mod grass_macros; + +grass_error!( + unitless_nan_str_slice_start_at, + "a {\n color: str-slice(\"\", (0/0));\n}\n", + "Error: NaN is not an int." +); +grass_error!( + unitless_nan_str_slice_end_at, + "a {\n color: str-slice(\"\", 0, (0/0));\n}\n", + "Error: NaN is not an int." +); +grass_error!( + unitless_nan_str_insert_index, + "a {\n color: str-insert(\"\", \"\", (0/0));\n}\n", + "Error: $index: NaN is not an int." +); +grass_test!( + unitless_nan_percentage_number, + "a {\n color: percentage((0/0));\n}\n", + "a {\n color: NaN%;\n}\n" +); +grass_test!( + unitless_nan_abs_number, + "a {\n color: abs((0/0));\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_error!( + unitless_nan_round_number, + "a {\n color: round((0/0));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_error!( + unitless_nan_ceil_number, + "a {\n color: ceil((0/0));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_error!( + unitless_nan_floor_number, + "a {\n color: floor((0/0));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_error!( + unitless_nan_random_limit, + "a {\n color: random((0/0));\n}\n", + "Error: $limit: NaN is not an int." +); +grass_error!( + unitless_nan_nth_n, + "a {\n color: nth([a], (0/0));\n}\n", + "Error: $n: NaN is not an int." +); +grass_error!( + unitless_nan_set_nth_n, + "a {\n color: set-nth([a], (0/0), b);\n}\n", + "Error: $n: NaN is not an int." +); +grass_test!( + unitless_nan_min_first_arg, + "$n: (0/0);\na {\n color: min($n, 1px);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + unitless_nan_min_last_arg, + "$n: (0/0);\na {\n color: min(1px, $n);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + unitless_nan_min_middle_arg, + "$n: (0/0);\na {\n color: min(1px, $n, 0);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + unitless_nan_max_first_arg, + "$n: (0/0);\na {\n color: max($n, 1px);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + unitless_nan_max_last_arg, + "$n: (0/0);\na {\n color: max(1px, $n);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + unitless_nan_max_middle_arg, + "$n: (0/0);\na {\n color: max(1px, $n, 0);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_error!( + unitful_nan_str_slice_start, + "@use \"sass:math\";\na {\n color: str-slice(\"\", math.acos(2));\n}\n", + "Error: $start: Expected NaNdeg to have no units." +); +grass_error!( + unitful_nan_str_slice_end, + "@use \"sass:math\";\na {\n color: str-slice(\"\", 0, math.acos(2));\n}\n", + "Error: $end: Expected NaNdeg to have no units." +); +grass_error!( + unitful_nan_str_insert_index, + "@use \"sass:math\";\na {\n color: str-insert(\"\", \"\", math.acos(2));\n}\n", + "Error: $index: Expected NaNdeg to have no units." +); +grass_error!( + unitful_nan_percentage, + "@use \"sass:math\";\na {\n color: percentage(math.acos(2));\n}\n", + "Error: $number: Expected NaNdeg to have no units." +); +grass_error!( + unitful_nan_round, + "@use \"sass:math\";\na {\n color: round(math.acos(2));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_error!( + unitful_nan_ceil, + "@use \"sass:math\";\na {\n color: ceil(math.acos(2));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_error!( + unitful_nan_floor, + "@use \"sass:math\";\na {\n color: floor(math.acos(2));\n}\n", + "Error: Infinity or NaN toInt" +); +grass_test!( + unitful_nan_abs, + "@use \"sass:math\";\na {\n color: abs(math.acos(2));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_error!( + unitful_nan_random, + "@use \"sass:math\";\na {\n color: random(math.acos(2));\n}\n", + "Error: $limit: NaNdeg is not an int." +); +grass_test!( + unitful_nan_min_first_arg, + "@use \"sass:math\";\na {\n color: min(math.acos(2), 1px);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + unitful_nan_min_last_arg, + "@use \"sass:math\";\na {\n color: min(1px, math.acos(2));\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + unitful_nan_min_middle_arg, + "@use \"sass:math\";\na {\n color: min(1px, math.acos(2), 0);\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + unitful_nan_max_first_arg, + "@use \"sass:math\";\na {\n color: max(math.acos(2), 1px);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +grass_test!( + unitful_nan_max_last_arg, + "@use \"sass:math\";\na {\n color: max(1px, math.acos(2));\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + unitful_nan_max_middle_arg, + "@use \"sass:math\";\na {\n color: max(1px, math.acos(2), 0);\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_error!( + unitful_nan_nth_n, + "@use \"sass:math\";\na {\n color: nth([a], math.acos(2));\n}\n", + "Error: $n: NaNdeg is not an int." +); +grass_error!( + unitful_nan_set_nth_n, + "@use \"sass:math\";\na {\n color: set-nth([a], math.acos(2), b);\n}\n", + "Error: $n: NaNdeg is not an int." +); +grass_test!( + nan_unary_negative, + "a {\n color: -(0/0);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + nan_unary_plus, + "a {\n color: +(0/0);\n}\n", + "a {\n color: NaN;\n}\n" +); +grass_test!( + nan_unary_div, + "a {\n color: /(0/0);\n}\n", + "a {\n color: /NaN;\n}\n" +); diff --git a/css/parser/tests/grass_not.rs b/css/parser/tests/grass_not.rs new file mode 100644 index 000000000000..164311acc43b --- /dev/null +++ b/css/parser/tests/grass_not.rs @@ -0,0 +1,38 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + not_number, + "a {\n color: not 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + not_true, + "a {\n color: not true;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + not_false, + "a {\n color: not false;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + not_null, + "a {\n color: not null;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + not_unquoted, + "a {\n color: not foo;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + not_not_true, + "a {\n color: not not true;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + not_not_false, + "a {\n color: not not false;\n}\n", + "a {\n color: false;\n}\n" +); diff --git a/css/parser/tests/grass_null.rs b/css/parser/tests/grass_null.rs new file mode 100644 index 000000000000..d055ba0d8921 --- /dev/null +++ b/css/parser/tests/grass_null.rs @@ -0,0 +1,38 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + null_in_parens_in_list, + "a {\n color: (null), (null), 3, 4;\n}\n", + "a {\n color: 3, 4;\n}\n" +); +grass_test!( + null_counted_in_list_length, + "a {\n color: length(null null null);\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + simple_null_list_not_emitted, + "a {\n color: null null null;\n}\n", + "" +); +grass_test!( + paren_null_list_not_emitted, + "a {\n color: (null null null);\n}\n", + "" +); +grass_test!( + bracketed_null_list_not_emitted, + "a {\n color: [null null null];\n}\n", + "" +); +grass_test!( + negative_null_in_var, + "a {\n $x: null;\n color: -$x;\n}\n", + "a {\n color: -;\n}\n" +); +grass_test!( + null_is_case_sensitive, + "a {\n color: NULL;\n}\n", + "a {\n color: NULL;\n}\n" +); diff --git a/css/parser/tests/grass_number.rs b/css/parser/tests/grass_number.rs new file mode 100644 index 000000000000..c33c89d41a12 --- /dev/null +++ b/css/parser/tests/grass_number.rs @@ -0,0 +1,177 @@ +#[macro_use] +mod grass_macros; + +// this is `1` for node-sass, but .999999etc for web compiler +grass_test!( + precision_does_not_round_up, + "a {\n color: 0.99999999991;\n}\n", + "a {\n color: 0.9999999999;\n}\n" +); +// this is `1` for node-sass, but .999999etc for web compiler +grass_test!( + precision_does_round_up, + "a {\n color: 1.00000000009;\n}\n", + "a {\n color: 1.0000000001;\n}\n" +); +grass_test!( + many_nines_becomes_one, + "a {\n color: 0.9999999999999999;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + many_nines_becomes_one_neg, + "a {\n color: -0.9999999999999999;\n}\n", + "a {\n color: -1;\n}\n" +); +grass_test!( + negative_zero, + "a {\n color: -0;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + decimal_is_zero, + "a {\n color: 1.0000;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!(many_nines_not_rounded, "a {\n color: 0.999999;\n}\n"); +grass_test!(positive_integer, "a {\n color: 1;\n}\n"); +grass_test!(negative_integer, "a {\n color: -1;\n}\n"); +grass_test!( + positive_float_no_leading_zero, + "a {\n color: .1;\n}\n", + "a {\n color: 0.1;\n}\n" +); +grass_test!( + negative_float_no_leading_zero, + "a {\n color: -.1;\n}\n", + "a {\n color: -0.1;\n}\n" +); +grass_test!(positive_float_leading_zero, "a {\n color: 0.1;\n}\n"); +grass_test!(negative_float_leading_zero, "a {\n color: -0.1;\n}\n"); +grass_test!( + negative_near_zero_no_sign, + "a {\n color: -0.000000000001;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + equality_unit_conversions, + "a {\n color: 1in == 96px;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + positive_scientific_notation, + "a {\n color: 1e5;\n}\n", + "a {\n color: 100000;\n}\n" +); +grass_test!( + positive_scientific_notation_leading_zeroes, + "a {\n color: 1e05;\n}\n", + "a {\n color: 100000;\n}\n" +); +grass_test!( + positive_scientific_notation_capital, + "a {\n color: 1E5;\n}\n", + "a {\n color: 100000;\n}\n" +); +grass_test!( + negative_scientific_notation, + "a {\n color: 1e-5;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +grass_test!( + negative_scientific_notation_leading_zeroes, + "a {\n color: 1e-05;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +grass_test!( + negative_scientific_notation_capital, + "a {\n color: 1E-5;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +grass_test!( + positive_scientific_notation_decimal, + "a {\n color: 1.2e5;\n}\n", + "a {\n color: 120000;\n}\n" +); +grass_test!( + negative_scientific_notation_decimal, + "a {\n color: 1.2e-5;\n}\n", + "a {\n color: 0.000012;\n}\n" +); +grass_test!(unit_e, "a {\n color: 1e;\n}\n"); +grass_test!( + positive_scientific_notation_zero, + "a {\n color: 1e0;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + negative_scientific_notation_zero, + "a {\n color: 1e-0;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + scientific_notation_decimal, + "a {\n color: 1.2e5.5;\n}\n", + "a {\n color: 120000 0.5;\n}\n" +); +grass_test!( + binary_op_with_e_as_unit, + "a {\n color: 1e - 2;\n}\n", + "a {\n color: -1e;\n}\n" +); +grass_error!( + scientific_notation_nothing_after_dash_in_style, + "a {\n color: 1e-;\n}\n", + "Error: Expected digit." +); +grass_error!( + scientific_notation_nothing_after_dash, + "a {\n color: 1e-", + "Error: Expected digit." +); +grass_error!( + scientific_notation_whitespace_after_dash, + "a {\n color: 1e- 2;\n}\n", + "Error: Expected digit." +); +grass_error!( + scientific_notation_ident_char_after_dash, + "a {\n color: 1e-a;\n}\n", + "Error: Expected digit." +); +grass_test!( + number_overflow_from_addition, + "a {\n color: 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999 + + 999999999999999999;\n}\n", + "a {\n color: 9999999999999999990;\n}\n" +); +grass_test!( + number_overflow_from_multiplication, + "a {\n color: 999999999999999999 * 10;\n}\n", + "a {\n color: 9999999999999999990;\n}\n" +); +grass_test!( + number_overflow_from_division, + "a {\n color: (999999999999999999 / .1);\n}\n", + "a {\n color: 9999999999999999990;\n}\n" +); +// we use arbitrary precision, so it is necessary to limit the size of exponents +// in order to prevent hangs +grass_error!( + scientific_notation_too_positive, + "a {\n color: 1e100;\n}\n", + "Error: Exponent too large." +); +grass_error!( + scientific_notation_too_negative, + "a {\n color: 1e-100;\n}\n", + "Error: Exponent too negative." +); diff --git a/css/parser/tests/grass_or.rs b/css/parser/tests/grass_or.rs new file mode 100644 index 000000000000..7eecdcb2d4fd --- /dev/null +++ b/css/parser/tests/grass_or.rs @@ -0,0 +1,78 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + one_or_two, + "a {\n color: 1 or 2;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + two_or_one, + "a {\n color: 2 or 1;\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + true_or_true, + "a {\n color: true or true;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + true_or_false, + "a {\n color: true or false;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + false_or_true, + "a {\n color: false or true;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + false_or_false, + "a {\n color: false or false;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + null_or_one, + "a {\n color: null or 1;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + one_or_null, + "a {\n color: 1 or null;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + one_or_two_or_three, + "a {\n color: 1 or 2 or 3;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + part_of_binop, + "a {\n color: 1 - or;\n}\n", + "a {\n color: 1-or;\n}\n" +); +grass_test!( + part_of_binop_casing, + "a {\n color: 1 - OR;\n}\n", + "a {\n color: 1-OR;\n}\n" +); +grass_test!( + short_circuits_when_lhs_is_true, + "a {\n color: true or red % foo;\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + does_not_short_circuit_when_lhs_is_false, + "a {\n color: false or red % foo;\n}\n", + "Error: Undefined operation \"red % foo\"." +); +grass_test!( + short_circuiting_in_comma_separated_list, + "a {\n color: true or red % foo, red;\n}\n", + "a {\n color: true, red;\n}\n" +); +grass_error!( + properly_bubbles_error_when_invalid_char_after_or, + "a {\n color: true or? foo;\n}\n", + "Error: expected \";\"." +); diff --git a/css/parser/tests/grass_order_of_operations.rs b/css/parser/tests/grass_order_of_operations.rs new file mode 100644 index 000000000000..463da3d13cf9 --- /dev/null +++ b/css/parser/tests/grass_order_of_operations.rs @@ -0,0 +1,43 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + addition_then_division, + "a {\n color: 3+3/4;\n}\n", + "a {\n color: 3.75;\n}\n" +); +grass_test!( + division_then_addition, + "a {\n color: 3/4 + 3;\n}\n", + "a {\n color: 3.75;\n}\n" +); +grass_test!( + addition_then_multiplication, + "a {\n color: 4 + 2 * 3;\n}\n", + "a {\n color: 10;\n}\n" +); +grass_test!( + multiplication_then_addition, + "a {\n color: 4 * 2 + 3;\n}\n", + "a {\n color: 11;\n}\n" +); +grass_test!( + comparison, + "a {\n color: 1 < 1 and 1 < 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + equals_then_or, + "a {\n color: a or b==c;\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + not_equals_then_or, + "a {\n color: a or b !=c;\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + leftmost_is_evaluated_first_when_same_precedence, + "a {\n color: 1 / 2 * 1em;\n}\n", + "a {\n color: 0.5em;\n}\n" +); diff --git a/css/parser/tests/grass_ordering.rs b/css/parser/tests/grass_ordering.rs new file mode 100644 index 000000000000..c57efbb818b1 --- /dev/null +++ b/css/parser/tests/grass_ordering.rs @@ -0,0 +1,68 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + two_greater_than_or_equal_one, + "a {\n color: 2 >= 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + one_greater_than_or_equal_one, + "a {\n color: 1 >= 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + zero_greater_than_or_equal_one, + "a {\n color: 0 >= 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + two_greater_than_one, + "a {\n color: 2 > 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + one_greater_than_one, + "a {\n color: 1 > 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + zero_greater_than_one, + "a {\n color: 0 > 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + two_less_than_or_equal_one, + "a {\n color: 2 <= 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + one_less_than_or_equal_one, + "a {\n color: 1 <= 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + zero_less_than_or_equal_one, + "a {\n color: 0 <= 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + two_less_than_one, + "a {\n color: 2 < 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + one_less_than_one, + "a {\n color: 1 < 1;\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + zero_less_than_one, + "a {\n color: 0 < 1;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + ord_the_same_as_partial_ord, + "a {\n color: 2in > 1cm;\n}\n", + "a {\n color: true;\n}\n" +); diff --git a/css/parser/tests/grass_plain_css_fn.rs b/css/parser/tests/grass_plain_css_fn.rs new file mode 100644 index 000000000000..a97416da1fa4 --- /dev/null +++ b/css/parser/tests/grass_plain_css_fn.rs @@ -0,0 +1,86 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + type_is_string, + "a {\n color: type-of(foo(1+1));\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + evaluates_arguments, + "a {\n color: foo(1+1);\n}\n", + "a {\n color: foo(2);\n}\n" +); +grass_test!( + arguments_are_comma_separated, + "a {\n color: foo(1+1, 2+3, 4+5);\n}\n", + "a {\n color: foo(2, 5, 9);\n}\n" +); +grass_test!( + converts_sql_quotes, + "a {\n color: foo('hi');\n}\n", + "a {\n color: foo(\"hi\");\n}\n" +); +grass_test!( + super_selector, + "a {\n color: foo(&);\n}\n", + "a {\n color: foo(a);\n}\n" +); +grass_test!( + nested_plain_css_fn, + "a {\n color: foo(foo(foo(foo(1+1))));\n}\n", + "a {\n color: foo(foo(foo(foo(2))));\n}\n" +); +grass_error!( + disallows_named_arguments, + "a {\n color: foo($a: 1+1);\n}\n", + "Error: Plain CSS functions don't support keyword arguments." +); +grass_test!( + evalutes_variables, + "a {\n $primary: #f2ece4;\n $accent: #e1d7d2;\n color: radial-gradient($primary, \ + $accent);\n}\n", + "a {\n color: radial-gradient(#f2ece4, #e1d7d2);\n}\n" +); +grass_test!( + fn_named_not_is_evaluated_as_unary_op, + "a {\n color: not(true);\n}\n", + "a {\n color: false;\n}\n" +); +grass_test!( + fn_named_true_is_plain_css, + "a {\n color: true(true);\n}\n", + "a {\n color: true(true);\n}\n" +); +grass_test!( + fn_named_false_is_plain_css, + "a {\n color: false(true);\n}\n", + "a {\n color: false(true);\n}\n" +); +grass_test!( + fn_named_null_is_plain_css, + "a {\n color: null(true);\n}\n", + "a {\n color: null(true);\n}\n" +); +grass_test!( + fn_named_and_is_evaluated_as_binop, + "a {\n color: true and(foo);\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + fn_named_or_is_evaluated_as_binop, + "a {\n color: true or(foo);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + #[ignore = "this is not currently parsed correctly"] + fn_named_and_alone_is_not_evaluated_as_binop, + "a {\n color: and(foo);\n}\n", + "a {\n color: and(foo);\n}\n" +); +grass_test!( + #[ignore = "this is not currently parsed correctly"] + fn_named_or_alone_is_not_evaluated_as_binop, + "a {\n color: or(foo);\n}\n", + "a {\n color: or(foo);\n}\n" +); diff --git a/css/parser/tests/grass_rsass_issues.rs b/css/parser/tests/grass_rsass_issues.rs new file mode 100644 index 000000000000..9f0f549f6078 --- /dev/null +++ b/css/parser/tests/grass_rsass_issues.rs @@ -0,0 +1,27 @@ +//! Test cases adapted verbatim from the rsass issue tracker +//! https://github.com/kaj/rsass/issues?q=is%3Aissue + +#[macro_use] +mod grass_macros; + +grass_test!( + /// https://github.com/kaj/rsass/issues/41 + issue_41, + "@function str-replace($string, $search, $replace: \"\") { + $index: str-index($string, $search); + + @if $index { + @return str-slice($string, 1, $index - 1)+$replace+str-replace(str-slice($string, $index + \ + str-length($search)), $search, $replace); + } + + @return $index; + } + + $x: str-replace(url(\"a#b#c\"), \"#\", \":\"); + + a { + color: $x; + }", + "a {\n color: url(\"a:b:;\n}\n" +); diff --git a/css/parser/tests/grass_scope.rs b/css/parser/tests/grass_scope.rs new file mode 100644 index 000000000000..34e808dd1b25 --- /dev/null +++ b/css/parser/tests/grass_scope.rs @@ -0,0 +1,83 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + scoping_var_decl_inner_ruleset, + "a {\n $color: red;\n b {\n $color: blue;\n }\n color: $color;\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + basic_global, + "a {\n $color: red !global;\n}\n\nb {\n color: $color;\n}\n", + "b {\n color: red;\n}\n" +); +grass_test!( + global_inserted_into_local_and_global_scopes, + "$foo: 42;\n\n.foo {\n content: $foo;\n $foo: 1337 !global;\n content: $foo;\n}\n\n.bar \ + {\n content: $foo;\n}\n", + ".foo {\n content: 42;\n content: 1337;\n}\n\n.bar {\n content: 1337;\n}\n" +); +grass_test!( + global_in_mixin, + "$y: a;\n@mixin foo {\n $y: b !global;\n}\na {\n @include foo;\n color: $y;\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + local_variable_exists_in_fn_mixin_scope, + "@function exists-fn($name) { + @return variable-exists($name); + } + + @mixin exists-mixin($name) { + color: variable-exists($name); + } + + a { + $x: foo; + @include exists-mixin(x); + color: exists-fn(x); + }", + "a {\n color: false;\n color: false;\n}\n" +); +grass_test!( + variable_redeclarations_propagate_to_outer_scopes, + " + a { + $a: red; + b { + $a: blue; + c { + d { + $a: orange; + color: $a; + } + color: $a; + } + color: $a; + } + color: $a; + } + ", + "a {\n color: orange;\n}\na b {\n color: orange;\n}\na b c {\n color: orange;\n}\na b c d \ + {\n color: orange;\n}\n" +); +grass_test!( + local_variable_exists_in_inner_fn_mixin_scope, + "a { + $x: foo; + + a { + @function exists-fn-inner($name) { + @return variable-exists($name); + } + + @mixin exists-mixin-inner($name) { + color: variable-exists($name); + } + + color: exists-fn-inner(x); + @include exists-mixin-inner(x); + } + }", + "a a {\n color: true;\n color: true;\n}\n" +); diff --git a/css/parser/tests/grass_selector_append.rs b/css/parser/tests/grass_selector_append.rs new file mode 100644 index 000000000000..7eb257856646 --- /dev/null +++ b/css/parser/tests/grass_selector_append.rs @@ -0,0 +1,79 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + classes_single, + "a {\n color: selector-append(\".c\", \".d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +grass_test!( + classes_multiple, + "a {\n color: selector-append(\".c, .d\", \".e, .f\");\n}\n", + "a {\n color: .c.e, .c.f, .d.e, .d.f;\n}\n" +); +grass_test!( + suffix_single, + "a {\n color: selector-append(\".c\", \"d\");\n}\n", + "a {\n color: .cd;\n}\n" +); +grass_test!( + suffix_multiple, + "a {\n color: selector-append(\".c, .d\", \"e, f\");\n}\n", + "a {\n color: .ce, .cf, .de, .df;\n}\n" +); +grass_test!( + suffix_descendant, + "a {\n color: selector-append(\"c d\", \"e f\");\n}\n", + "a {\n color: c de f;\n}\n" +); +grass_test!( + one_arg, + "a {\n color: selector-append(\".c.d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +grass_test!( + many_args, + "a {\n color: selector-append(\".c\", \".d\", \".e\");\n}\n", + "a {\n color: .c.d.e;\n}\n" +); +grass_test!( + paren_first_arg, + "a {\n color: selector-append((c, d e), f);\n}\n", + "a {\n color: cf, d ef;\n}\n" +); +grass_test!( + paren_second_arg, + "a {\n color: selector-append(c, (d, e f));\n}\n", + "a {\n color: cd, ce f;\n}\n" +); +grass_test!( + output_structure, + "a {\n color: selector-append(\"c d, e f\", \"g\") == (\"c\" \"dg\", \"e\" \"fg\");\n}\n", + "a {\n color: true;\n}\n" +); +grass_error!( + universal_in_second_arg, + "a {\n color: selector-append(\".c\", \"*\");\n}\n", + "Error: Can't append * to .c." +); +grass_error!( + parent_in_second_arg, + "a {\n color: selector-append(\"c\", \"&\");\n}\n", + "Error: Parent selectors aren't allowed here." +); +grass_error!( + malformed_selector_in_first_arg, + "a {\n color: selector-append(\"[c\", \".d\");\n}\n", + "Error: expected more input." +); +grass_error!( + invalid_type_in_first_arg, + "a {\n color: selector-append(\"c\", 1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a \ + list of lists of strings." +); +grass_error!( + no_args, + "a {\n color: selector-append();\n}\n", + "Error: $selectors: At least one selector must be passed." +); diff --git a/css/parser/tests/grass_selector_extend.rs b/css/parser/tests/grass_selector_extend.rs new file mode 100644 index 000000000000..a1ea4f700fa8 --- /dev/null +++ b/css/parser/tests/grass_selector_extend.rs @@ -0,0 +1,306 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + simple_attribute_equal, + "a {\n color: selector-extend(\"[c=d]\", \"[c=d]\", \"e\");\n}\n", + "a {\n color: [c=d], e;\n}\n" +); +grass_test!( + simple_attribute_unequal_name, + "a {\n color: selector-extend(\"[c=d]\", \"[e=d]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +grass_test!( + simple_attribute_unequal_value, + "a {\n color: selector-extend(\"[c=d]\", \"[c=e]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +grass_test!( + simple_attribute_unequal_operator, + "a {\n color: selector-extend(\"[c=d]\", \"[c^=e]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +grass_test!( + simple_class_equal, + "a {\n color: selector-extend(\".c\", \".c\", \"e\");\n}\n", + "a {\n color: .c, e;\n}\n" +); +grass_test!( + simple_class_unequal, + "a {\n color: selector-extend(\".c\", \".d\", \"e\");\n}\n", + "a {\n color: .c;\n}\n" +); +grass_test!( + simple_id_equal, + "a {\n color: selector-extend(\"#c\", \"#c\", \"e\");\n}\n", + "a {\n color: #c, e;\n}\n" +); +grass_test!( + simple_id_unequal, + "a {\n color: selector-extend(\"#c\", \"#d\", \"e\");\n}\n", + "a {\n color: #c;\n}\n" +); +grass_test!( + simple_placeholder_equal, + "a {\n color: selector-extend(\"%c\", \"%c\", \"e\");\n}\n", + "a {\n color: %c, e;\n}\n" +); +grass_test!( + simple_placeholder_unequal, + "a {\n color: selector-extend(\"%c\", \"%d\", \"e\");\n}\n", + "a {\n color: %c;\n}\n" +); +grass_test!( + simple_type_equal, + "a {\n color: selector-extend(\"c\", \"c\", \"e\");\n}\n", + "a {\n color: c, e;\n}\n" +); +grass_test!( + simple_type_unequal, + "a {\n color: selector-extend(\"c\", \"d\", \"e\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + simple_type_and_universal, + "a {\n color: selector-extend(\"c\", \"*\", \"d\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"c|d\", \"c|d\", \"e\");\n}\n", + "a {\n color: c|d, e;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"c|d\", \"d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"c|d\", \"|d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +grass_test!( + simple_type_explicit_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"c|d\", \"*|d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"|c\", \"d|c\", \"e\");\n}\n", + "a {\n color: |c;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"|c\", \"c\", \"d\");\n}\n", + "a {\n color: |c;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"|c\", \"|c\", \"d\");\n}\n", + "a {\n color: |c, d;\n}\n" +); +grass_test!( + simple_type_empty_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"|c\", \"*|c\", \"d\");\n}\n", + "a {\n color: |c;\n}\n" +); +grass_test!( + simple_type_universal_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"*|c\", \"d|c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +grass_test!( + simple_type_universal_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"*|c\", \"c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +grass_test!( + simple_type_universal_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"*|c\", \"|c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +grass_test!( + simple_type_universal_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"*|c\", \"*|c\", \"d\");\n}\n", + "a {\n color: *|c, d;\n}\n" +); +grass_test!( + simple_pseudo_class_no_arg_equal, + "a {\n color: selector-extend(\":c\", \":c\", \"e\");\n}\n", + "a {\n color: :c, e;\n}\n" +); +grass_test!( + simple_pseudo_class_no_arg_unequal, + "a {\n color: selector-extend(\":c\", \":d\", \"e\");\n}\n", + "a {\n color: :c;\n}\n" +); +grass_test!( + simple_pseudo_class_no_arg_and_element, + "a {\n color: selector-extend(\":c\", \"::c\", \"e\");\n}\n", + "a {\n color: :c;\n}\n" +); +grass_test!( + simple_pseudo_element_no_arg_and_element_equal, + "a {\n color: selector-extend(\"::c\", \"::c\", \"e\");\n}\n", + "a {\n color: ::c, e;\n}\n" +); +grass_test!( + simple_pseudo_element_no_arg_and_class, + "a {\n color: selector-extend(\"::c\", \":c\", \"e\");\n}\n", + "a {\n color: ::c;\n}\n" +); +grass_test!( + simple_pseudo_class_arg_equal, + "a {\n color: selector-extend(\":c(@#$)\", \":c(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$), e;\n}\n" +); +grass_test!( + simple_pseudo_class_arg_unequal_name, + "a {\n color: selector-extend(\":c(@#$)\", \":d(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_class_arg_unequal_arg, + "a {\n color: selector-extend(\":c(@#$)\", \":c(*&^)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_class_arg_unequal_no_arg, + "a {\n color: selector-extend(\":c(@#$)\", \":c\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_class_arg_and_element, + "a {\n color: selector-extend(\":c(@#$)\", \"::c(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_element_arg_and_element_equal, + "a {\n color: selector-extend(\"::c(@#$)\", \"::c(@#$)\", \"e\");\n}\n", + "a {\n color: ::c(@#$), e;\n}\n" +); +grass_test!( + simple_pseudo_element_arg_and_class, + "a {\n color: selector-extend(\"::c(@#$)\", \":c(@#$)\", \"e\");\n}\n", + "a {\n color: ::c(@#$);\n}\n" +); +grass_test!( + complex_parent_without_grandparents_simple, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e\");\n}\n", + "a {\n color: .c .d, .e .d;\n}\n" +); +grass_test!( + complex_parent_without_grandparents_complex, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e .f\");\n}\n", + "a {\n color: .c .d, .e .f .d;\n}\n" +); +grass_test!( + complex_parent_without_grandparents_list, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e, .f\");\n}\n", + "a {\n color: .c .d, .e .d, .f .d;\n}\n" +); +grass_test!( + complex_parent_with_grandparents_simple, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f\");\n}\n", + "a {\n color: .c .d .e, .c .f .e;\n}\n" +); +grass_test!( + complex_parent_with_grandparents_complex, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f .g\");\n}\n", + "a {\n color: .c .d .e, .c .f .g .e, .f .c .g .e;\n}\n" +); +grass_test!( + complex_parent_with_grandparents_list, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f, .g\");\n}\n", + "a {\n color: .c .d .e, .c .f .e, .c .g .e;\n}\n" +); +grass_test!( + complex_trailing_combinator_child, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e >\");\n}\n", + "a {\n color: .c .d, .e > .d;\n}\n" +); +grass_test!( + complex_trailing_combinator_sibling, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e ~\");\n}\n", + "a {\n color: .c .d, .e ~ .d;\n}\n" +); +grass_test!( + complex_trailing_combinator_next_sibling, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n", + "a {\n color: .c .d, .e + .d;\n}\n" +); +grass_test!( + list_partial_no_op, + "a {\n color: selector-extend(\"c, d\", \"d\", \"e\");\n}\n", + "a {\n color: c, d, e;\n}\n" +); +grass_test!( + combinator_in_selector, + "a {\n color: selector-extend(\"a > b\", \"foo\", \"bar\");\n}\n", + "a {\n color: a > b;\n}\n" +); +grass_test!( + combinator_in_selector_with_complex_child_and_complex_2_as_extender, + "a {\n color: selector-extend(\"a + b .c1\", \".c1\", \"a c\");\n}\n", + "a {\n color: a + b .c1, a + b a c, a a + b c;\n}\n" +); +grass_test!( + combinator_in_selector_with_complex_child_and_complex_3_as_extender, + "a {\n color: selector-extend(\"a + b .c1\", \".c1\", \"a b .c2\");\n}\n", + "a {\n color: a + b .c1, a a + b .c2;\n}\n" +); +grass_test!( + list_as_target_with_compound_selector, + "a {\n color: selector-extend(\".foo.bar\", \".foo, .bar\", \".x\");\n}\n", + "a {\n color: .foo.bar, .x;\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_simple, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \".d\");\n}\n", + "a {\n color: :not(.c):not(.d);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_list, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \".d, .e\");\n}\n", + "a {\n color: :not(.c):not(.d):not(.e);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_complex, + "a {\n color: selector-extend(\":not(.c .d)\", \".d\", \".e .f\");\n}\n", + "a {\n color: :not(.c .d):not(.c .e .f):not(.e .c .f);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_compound, + "a {\n color: selector-extend(\":not(.c.d)\", \".c\", \".e\");\n}\n", + "a {\n color: :not(.c.d):not(.d.e);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_contains_list, + "a {\n color: selector-extend(\":not(.c, .d)\", \".c\", \".e\");\n}\n", + "a {\n color: :not(.c, .e, .d);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_and_matches_list, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \":matches(.d, .e)\");\n}\n", + "a {\n color: :not(.c):not(.d):not(.e);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_and_matches_list_of_complex, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \":matches(.d .e, .f .g)\");\n}\n", + "a {\n color: :not(.c):not(.d .e):not(.f .g);\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_and_matches_in_compound, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \".d:matches(.e, .f)\");\n}\n", + "a {\n color: :not(.c):not(.d:matches(.e, .f));\n}\n" +); +grass_test!( + simple_pseudo_idempotent_not_and_not_in_extender, + "a {\n color: selector-extend(\":not(.c)\", \".c\", \":not(.d)\");\n}\n", + "a {\n color: :not(.c);\n}\n" +); +// todo: https://github.com/sass/sass-spec/blob/master/spec/core_functions/selector/extend/simple/pseudo/selector/idempotent.hrx +// (starting at line 113) +// todo: https://github.com/sass/sass-spec/tree/master/spec/core_functions/selector/extend/simple/pseudo/selector/ diff --git a/css/parser/tests/grass_selector_nest.rs b/css/parser/tests/grass_selector_nest.rs new file mode 100644 index 000000000000..c7eddfe49509 --- /dev/null +++ b/css/parser/tests/grass_selector_nest.rs @@ -0,0 +1,161 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + nest_one_arg, + "a {\n color: selector-nest(\"c\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + nest_many_args, + "a {\n color: selector-nest(\"c\", \"d\", \"e\", \"f\", \"g\");\n}\n", + "a {\n color: c d e f g;\n}\n" +); +grass_test!( + nest_parent_alone, + "a {\n color: selector-nest(\"c\", \"&\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + nest_parent_compound, + "a {\n color: selector-nest(\"c\", \"&.d\");\n}\n", + "a {\n color: c.d;\n}\n" +); +grass_test!( + nest_parent_with_suffix, + "a {\n color: selector-nest(\"c\", \"&d\");\n}\n", + "a {\n color: cd;\n}\n" +); +grass_test!( + nest_complex_parent_compound, + "a {\n color: selector-nest(\"c\", \"d &.e\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +grass_test!( + nest_complex_super_parent_compound, + "a {\n color: selector-nest(\"c d\", \"e &.f\");\n}\n", + "a {\n color: e c d.f;\n}\n" +); +grass_test!( + nest_parent_in_special_pseudo, + "a {\n color: selector-nest(\"c\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c);\n}\n" +); +grass_test!( + nest_complex_super_parent_in_special_pseudo, + "a {\n color: selector-nest(\"c d\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c d);\n}\n" +); +grass_test!( + nest_multiple_parent, + "a {\n color: selector-nest(\"c\", \"&.d &.e\");\n}\n", + "a {\n color: c.d c.e;\n}\n" +); +grass_test!( + nest_compound_parent_in_list, + "a {\n color: selector-nest(\"c\", \"&.d, e\");\n}\n", + "a {\n color: c.d, c e;\n}\n" +); +grass_test!( + nest_list_super, + "a {\n color: selector-nest(\"c, d\", \"e\");\n}\n", + "a {\n color: c e, d e;\n}\n" +); +grass_test!( + nest_list_sub, + "a {\n color: selector-nest(\"c\", \"d, e\");\n}\n", + "a {\n color: c d, c e;\n}\n" +); +grass_test!( + nest_three_args_list, + "a {\n color: selector-nest(\"c, d\", \"e, f\", \"g, h\");\n}\n", + "a {\n color: c e g, c e h, c f g, c f h, d e g, d e h, d f g, d f h;\n}\n" +); +grass_test!( + nest_super_list_parent_alone, + "a {\n color: selector-nest(\"c, d\", \"&\");\n}\n", + "a {\n color: c, d;\n}\n" +); +grass_test!( + nest_super_list_parent_compound, + "a {\n color: selector-nest(\"c, d\", \"&.e\");\n}\n", + "a {\n color: c.e, d.e;\n}\n" +); +grass_test!( + nest_super_list_parent_suffix, + "a {\n color: selector-nest(\"c, d\", \"&e\");\n}\n", + "a {\n color: ce, de;\n}\n" +); +grass_test!( + nest_super_list_parent_complex, + "a {\n color: selector-nest(\"c, d\", \"e &.f\");\n}\n", + "a {\n color: e c.f, e d.f;\n}\n" +); +grass_test!( + nest_super_list_parent_inside_pseudo, + "a {\n color: selector-nest(\"c, d\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c, d);\n}\n" +); +grass_test!( + nest_super_list_multiple_parent, + "a {\n color: selector-nest(\"c, d\", \"&.e &.f\");\n}\n", + "a {\n color: c.e c.f, c.e d.f, d.e c.f, d.e d.f;\n}\n" +); +grass_test!( + nest_super_list_sub_list_contains_parent, + "a {\n color: selector-nest(\"c, d\", \"&.e, f\");\n}\n", + "a {\n color: c.e, c f, d.e, d f;\n}\n" +); +grass_test!( + nest_comma_separated_list_as_super, + "a {\n color: selector-nest((c, d e), \"f\");\n}\n", + "a {\n color: c f, d e f;\n}\n" +); +grass_test!( + nest_comma_separated_list_as_sub, + "a {\n color: selector-nest(\"c\", (d, e f));\n}\n", + "a {\n color: c d, c e f;\n}\n" +); +grass_error!( + #[ignore = "https://github.com/sass/dart-sass/issues/966"] + disallows_parent_selector_as_first_arg, + "a {\n color: selector-nest(\"&\");\n}\n", + "Error: Parent selectors aren't allowed here." +); +grass_error!( + disallows_parent_not_at_start_of_compound_selector_attribute, + "a {\n color: selector-nest(\"[d]&\");\n}\n", + "Error: \"&\" may only used at the beginning of a compound selector." +); +grass_error!( + disallows_parent_not_at_start_of_compound_selector_type, + "a {\n color: selector-nest(\"d&\");\n}\n", + "Error: \"&\" may only used at the beginning of a compound selector." +); +grass_error!( + improperly_terminated_attribute_selector_first_arg, + "a {\n color: selector-nest(\"[d\");\n}\n", + "Error: expected more input." +); +grass_error!( + improperly_terminated_attribute_selector_second_arg, + "a {\n color: selector-nest(\"c\", \"[d\");\n}\n", + "Error: expected more input." +); +grass_error!( + unquoted_integer_first_arg, + "a {\n color: selector-nest(1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a \ + list of lists of strings." +); +grass_error!( + unquoted_integer_second_arg, + "a {\n color: selector-nest(\"c\", 1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a \ + list of lists of strings." +); +grass_error!( + empty_args, + "a {\n color: selector-nest();\n}\n", + "Error: $selectors: At least one selector must be passed." +); diff --git a/css/parser/tests/grass_selector_parse.rs b/css/parser/tests/grass_selector_parse.rs new file mode 100644 index 000000000000..1d07e23c51cb --- /dev/null +++ b/css/parser/tests/grass_selector_parse.rs @@ -0,0 +1,108 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + named_args, + "a {\n color: selector-parse($selector: \"c\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + simple_class, + "a {\n color: selector-parse(\".c\");\n}\n", + "a {\n color: .c;\n}\n" +); +grass_test!( + simple_id, + "a {\n color: selector-parse(\"#c\");\n}\n", + "a {\n color: #c;\n}\n" +); +grass_test!( + simple_placeholder, + "a {\n color: selector-parse(\"%c\");\n}\n", + "a {\n color: %c;\n}\n" +); +grass_test!( + simple_attribute, + "a {\n color: selector-parse(\"[c^=d]\");\n}\n", + "a {\n color: [c^=d];\n}\n" +); +grass_test!( + simple_universal, + "a {\n color: selector-parse(\"*\");\n}\n", + "a {\n color: *;\n}\n" +); +grass_test!( + simple_pseudo, + "a {\n color: selector-parse(\":c\");\n}\n", + "a {\n color: :c;\n}\n" +); +grass_test!( + pseudo_weird_args, + "a {\n color: selector-parse(\":c(@#$)\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + pseudo_matches_with_list_args, + "a {\n color: selector-parse(\":matches(b, c)\");\n}\n", + "a {\n color: :matches(b, c);\n}\n" +); +grass_test!( + pseudo_element, + "a {\n color: selector-parse(\"::c\");\n}\n", + "a {\n color: ::c;\n}\n" +); +grass_test!( + pseudo_element_args, + "a {\n color: selector-parse(\"::c(@#$)\");\n}\n", + "a {\n color: ::c(@#$);\n}\n" +); +grass_test!( + pseudo_element_slotted_list_args_output, + "a {\n color: selector-parse(\"::slotted(b, c)\");\n}\n", + "a {\n color: ::slotted(b, c);\n}\n" +); +grass_test!( + pseudo_element_slotted_list_args_structure, + "a {\n color: selector-parse(\"::slotted(b, c)\") == (append((), \"::slotted(b, c)\"),);\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + multiple_compound, + "a {\n color: selector-parse(\"b.c:d\");\n}\n", + "a {\n color: b.c:d;\n}\n" +); +grass_test!( + multiple_complex, + "a {\n color: selector-parse(\"b c d\");\n}\n", + "a {\n color: b c d;\n}\n" +); +grass_test!( + sibling_combinator, + "a {\n color: selector-parse(\"b ~ c ~ d\");\n}\n", + "a {\n color: b ~ c ~ d;\n}\n" +); +grass_test!( + adjacent_combinator, + "a {\n color: selector-parse(\"b + c + d\");\n}\n", + "a {\n color: b + c + d;\n}\n" +); +grass_test!( + child_combinator, + "a {\n color: selector-parse(\"b > c > d\");\n}\n", + "a {\n color: b > c > d;\n}\n" +); +grass_test!( + comma_and_space_list, + "a {\n color: selector-parse(\"b c, d e, f g\");\n}\n", + "a {\n color: b c, d e, f g;\n}\n" +); +grass_error!( + invalid_selector, + "a {\n color: selector-parse(\"!!!!!!!!\");\n}\n", + "Error: $selector: expected selector." +); +grass_error!( + selector_contains_curly_brace, + "a {\n color: selector-parse(\"a {\");\n}\n", + "Error: $selector: expected selector." +); diff --git a/css/parser/tests/grass_selector_replace.rs b/css/parser/tests/grass_selector_replace.rs new file mode 100644 index 000000000000..e70f9017c20c --- /dev/null +++ b/css/parser/tests/grass_selector_replace.rs @@ -0,0 +1,38 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + simple, + "a {\n color: selector-replace(\"c\", \"c\", \"d\");\n}\n", + "a {\n color: d;\n}\n" +); +grass_test!( + compound, + "a {\n color: selector-replace(\"c.d\", \"c\", \"e\");\n}\n", + "a {\n color: e.d;\n}\n" +); +grass_test!( + complex, + "a {\n color: selector-replace(\"c d\", \"d\", \"e f\");\n}\n", + "a {\n color: c e f, e c f;\n}\n" +); +grass_test!( + psuedo_matches, + "a {\n color: selector-replace(\":matches(c)\", \"c\", \"d\");\n}\n", + "a {\n color: :matches(d);\n}\n" +); +grass_test!( + psuedo_not, + "a {\n color: selector-replace(\":not(c)\", \"c\", \"d\");\n}\n", + "a {\n color: :not(d);\n}\n" +); +grass_test!( + no_op, + "a {\n color: selector-replace(\"c\", \"d\", \"e\");\n}\n", + "a {\n color: c;\n}\n" +); +grass_test!( + partial_no_op, + "a {\n color: selector-replace(\"c, d\", \"d\", \"e\");\n}\n", + "a {\n color: c, e;\n}\n" +); diff --git a/css/parser/tests/grass_selector_unify.rs b/css/parser/tests/grass_selector_unify.rs new file mode 100644 index 000000000000..9af08a33a1e0 --- /dev/null +++ b/css/parser/tests/grass_selector_unify.rs @@ -0,0 +1,679 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + no_overlap, + "a {\n color: selector-unify(\".c.d\", \".e.f\");\n}\n", + "a {\n color: .c.d.e.f;\n}\n" +); +grass_test!( + partial_overlap, + "a {\n color: selector-unify(\".c.d\", \".d.e\");\n}\n", + "a {\n color: .c.d.e;\n}\n" +); +grass_test!( + full_overlap, + "a {\n color: selector-unify(\".c.d\", \".c.d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +grass_test!( + order_element_at_start, + "a {\n color: selector-unify(\".c\", \"d\");\n}\n", + "a {\n color: d.c;\n}\n" +); +grass_test!( + order_pseudo_element_at_end, + "a {\n color: selector-unify(\"::c\", \".d\");\n}\n", + "a {\n color: .d::c;\n}\n" +); +grass_test!( + order_pseudo_class_at_end, + "a {\n color: selector-unify(\":c\", \".d\");\n}\n", + "a {\n color: .d:c;\n}\n" +); +grass_test!( + order_pseudo_element_after_pseudo_class, + "a {\n color: selector-unify(\"::c\", \":d\");\n}\n", + "a {\n color: :d::c;\n}\n" +); +grass_test!( + attribute_identical, + "a {\n color: selector-unify(\"[a]\", \"[a]\");\n}\n", + "a {\n color: [a];\n}\n" +); +grass_test!( + attribute_distinct, + "a {\n color: selector-unify(\"[a]\", \"[b]\");\n}\n", + "a {\n color: [a][b];\n}\n" +); +grass_test!( + class_identical, + "a {\n color: selector-unify(\".a\", \".a\");\n}\n", + "a {\n color: .a;\n}\n" +); +grass_test!( + class_distinct, + "a {\n color: selector-unify(\".a\", \".b\");\n}\n", + "a {\n color: .a.b;\n}\n" +); +grass_test!( + element_id, + "a {\n color: selector-unify(\"a\", \"#b\");\n}\n", + "a {\n color: a#b;\n}\n" +); +grass_test!( + id_identical, + "a {\n color: selector-unify(\"#a\", \"#a\");\n}\n", + "a {\n color: #a;\n}\n" +); +grass_test!( + id_distinct, + "a {\n color: selector-unify(\"#a\", \"#b\");\n}\n", + "" +); +grass_test!( + placeholder_identical, + "a {\n color: selector-unify(\"%a\", \"%a\");\n}\n", + "a {\n color: %a;\n}\n" +); +grass_test!( + placeholder_distinct, + "a {\n color: selector-unify(\"%a\", \"%b\");\n}\n", + "a {\n color: %a%b;\n}\n" +); +grass_test!( + universal_and_namespace, + "a {\n color: selector-unify(\"*\", \"a|b\");\n}\n", + "" +); +grass_test!( + universal_and_empty_namespace, + "a {\n color: selector-unify(\"*\", \"|b\");\n}\n", + "" +); +grass_test!( + universal_and_type, + "a {\n color: selector-unify(\"*\", \"a\");\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + universal_and_asterisk_namespace, + "a {\n color: selector-unify(\"*\", \"*|a\");\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + universal_with_namespace_and_same_namespace, + "a {\n color: selector-unify(\"a|*\", \"a|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +grass_test!( + universal_with_namespace_and_different_namespace, + "a {\n color: selector-unify(\"a|*\", \"c|b\");\n}\n", + "" +); +grass_test!( + universal_with_namespace_and_no_namespace, + "a {\n color: selector-unify(\"a|*\", \"|b\");\n}\n", + "" +); +grass_test!( + universal_with_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"a|*\", \"*|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +grass_test!( + universal_with_empty_namespace_and_empty_namespace, + "a {\n color: selector-unify(\"|*\", \"|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +grass_test!( + universal_with_empty_namespace_and_no_namespace, + "a {\n color: selector-unify(\"|*\", \"b\");\n}\n", + "" +); +grass_test!( + universal_with_empty_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"|*\", \"*|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_namespace, + "a {\n color: selector-unify(\"*|*\", \"a|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_empty_namespace, + "a {\n color: selector-unify(\"*|*\", \"|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_no_namespace, + "a {\n color: selector-unify(\"*|*\", \"b\");\n}\n", + "a {\n color: b;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"*|*\", \"*|b\");\n}\n", + "a {\n color: *|b;\n}\n" +); +grass_test!( + universal_with_no_namespace_and_namespace_on_universal, + "a {\n color: selector-unify(\"*\", \"a|*\");\n}\n", + "" +); +grass_test!( + universal_with_no_namespace_and_empty_namespace_on_universal, + "a {\n color: selector-unify(\"*\", \"|*\");\n}\n", + "" +); +grass_test!( + universal_with_no_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"*\", \"*\");\n}\n", + "a {\n color: *;\n}\n" +); +grass_test!( + universal_with_no_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"*\", \"*|*\");\n}\n", + "a {\n color: *;\n}\n" +); +grass_test!( + universal_with_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"c|*\", \"c|*\");\n}\n", + "a {\n color: c|*;\n}\n" +); +grass_test!( + universal_with_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"c|*\", \"|*\");\n}\n", + "" +); +grass_test!( + universal_with_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"c|*\", \"*\");\n}\n", + "" +); +grass_test!( + universal_with_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"c|*\", \"*|*\");\n}\n", + "a {\n color: c|*;\n}\n" +); +grass_test!( + universal_with_empty_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"|*\", \"b|*\");\n}\n", + "" +); +grass_test!( + universal_with_empty_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"|*\", \"|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +grass_test!( + universal_with_empty_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"|*\", \"*\");\n}\n", + "" +); +grass_test!( + universal_with_empty_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"|*\", \"*|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"*|*\", \"a|*\");\n}\n", + "a {\n color: a|*;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"*|*\", \"|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"*|*\", \"*|*\");\n}\n", + "a {\n color: *|*;\n}\n" +); +grass_test!( + universal_with_asterisk_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"*|*\", \"*\");\n}\n", + "a {\n color: *;\n}\n" +); +grass_test!( + complex_two_levels_same_first, + "a {\n color: selector-unify(\".c .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .s1.s2;\n}\n" +); +grass_test!( + complex_three_levels_same_first, + "a {\n color: selector-unify(\".c .s1-1 .s1-2\", \".c .s2-1 .s2-2\");\n}\n", + "a {\n color: .c .s1-1 .s2-1 .s1-2.s2-2, .c .s2-1 .s1-1 .s1-2.s2-2;\n}\n" +); +grass_test!( + complex_three_levels_same_second, + "a {\n color: selector-unify(\".s1-1 .d .s1-2\", \".s2-1 .d .s2-2\");\n}\n", + "a {\n color: .s1-1 .s2-1 .d .s1-2.s2-2, .s2-1 .s1-1 .d .s1-2.s2-2;\n}\n" +); +grass_test!( + second_is_super_selector, + "a {\n color: selector-unify(\"c\", \"d c.e\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +grass_test!( + first_is_super_selector, + "a {\n color: selector-unify(\"d c.e\", \"c\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +grass_test!( + second_parent_is_super_selector, + "a {\n color: selector-unify(\"c d\", \"c.e .f\");\n}\n", + "a {\n color: c.e d.f;\n}\n" +); +grass_test!( + first_parent_is_super_selector, + "a {\n color: selector-unify(\"c.e .f\", \"c d\");\n}\n", + "a {\n color: c.e d.f;\n}\n" +); +grass_test!( + two_level_distinct, + "a {\n color: selector-unify(\".c .d\", \".e .f\");\n}\n", + "a {\n color: .c .e .d.f, .e .c .d.f;\n}\n" +); +grass_test!( + three_level_distinct, + "a {\n color: selector-unify(\".c .d .e\", \".f .g .h\");\n}\n", + "a {\n color: .c .d .f .g .e.h, .f .g .c .d .e.h;\n}\n" +); +grass_test!( + two_level_super_selector, + "a {\n color: selector-unify(\".c.s1-1 .s1-2\", \".c .s2\");\n}\n", + "a {\n color: .c.s1-1 .s1-2.s2;\n}\n" +); +grass_test!( + three_level_outer_super_selector, + "a {\n color: selector-unify(\".c.s1-1 .s1-2 .s1-3\", \".c .s2-1 .s2-2\");\n}\n", + "a {\n color: .c.s1-1 .s1-2 .s2-1 .s1-3.s2-2, .c.s1-1 .s2-1 .s1-2 .s1-3.s2-2;\n}\n" +); +grass_test!( + three_level_inner_super_selector, + "a {\n color: selector-unify(\".s1-1 .c.s1-2 .s1-3\", \".s2-1 .c .s2-2\");\n}\n", + "a {\n color: .s1-1 .s2-1 .c.s1-2 .s1-3.s2-2, .s2-1 .s1-1 .c.s1-2 .s1-3.s2-2;\n}\n" +); +grass_test!( + combinator_child_and_descendant_distinct, + "a {\n color: selector-unify(\".c > .d\", \".e .f\");\n}\n", + "a {\n color: .e .c > .d.f;\n}\n" +); +grass_test!( + combinator_child_and_descendant_same, + "a {\n color: selector-unify(\".c > .s1\", \".c .s2\");\n}\n", + "a {\n color: .c > .s1.s2;\n}\n" +); +grass_test!( + combinator_child_and_descendant_super_selector, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c .s2\");\n}\n", + "a {\n color: .c.s1-1 > .s1-2.s2;\n}\n" +); +grass_test!( + combinator_child_and_descendant_overlap, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c.s2-1 .s2-2\");\n}\n", + "a {\n color: .c.s2-1 .c.s1-1 > .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_child_and_child_distinct, + "a {\n color: selector-unify(\".c > .d\", \".e > .f\");\n}\n", + "a {\n color: .e.c > .d.f;\n}\n" +); +grass_test!( + combinator_child_and_child_super_selector, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c > .s2\");\n}\n", + "a {\n color: .c.s1-1 > .s1-2.s2;\n}\n" +); +grass_test!( + combinator_child_and_child_overlap, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c.s2-1 > .s2-2\");\n}\n", + "a {\n color: .c.s2-1.s1-1 > .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_child_and_child_conflict, + "a {\n color: selector-unify(\"#s1-1 > .s1-2\", \"#s2-1 > .s2-2\");\n}\n", + "" +); +grass_test!( + combinator_child_and_sibling, + "a {\n color: selector-unify(\".c > .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c > .c ~ .s1.s2;\n}\n" +); +grass_test!( + combinator_child_and_next_sibling, + "a {\n color: selector-unify(\".c > .s1\", \".c + .s2\");\n}\n", + "a {\n color: .c > .c + .s1.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_descendant, + "a {\n color: selector-unify(\".c ~ .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .c ~ .s1.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_child, + "a {\n color: selector-unify(\".c ~ .s1\", \".c > .s2\");\n}\n", + "a {\n color: .c > .c ~ .s1.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_sibling_distinct, + "a {\n color: selector-unify(\".c ~ .d\", \".e ~ .f\");\n}\n", + "a {\n color: .c ~ .e ~ .d.f, .e ~ .c ~ .d.f, .e.c ~ .d.f;\n}\n" +); +grass_test!( + combinator_sibling_and_sibling_same, + "a {\n color: selector-unify(\".c ~ .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c ~ .s1.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c ~ .s2\");\n}\n", + "a {\n color: .c.s1-1 ~ .s1-2.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c.s2-1 ~ .s2-2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c.s2-1 ~ .s1-2.s2-2, .c.s2-1 ~ .c.s1-1 ~ .s1-2.s2-2, .c.s2-1.s1-1 ~ \ + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_sibling_and_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 ~ .s1-2\", \"#s2-1 ~ .s2-2\");\n}\n", + "a {\n color: #s1-1 ~ #s2-1 ~ .s1-2.s2-2, #s2-1 ~ #s1-1 ~ .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_sibling_and_next_sibling_distinct, + "a {\n color: selector-unify(\".c ~ .d\", \".e + .f\");\n}\n", + "a {\n color: .c ~ .e + .d.f, .e.c + .d.f;\n}\n" +); +grass_test!( + combinator_sibling_and_next_sibling_identical, + "a {\n color: selector-unify(\".c ~ .s1\", \".c + .s2\");\n}\n", + "a {\n color: .c + .s1.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_next_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c + .s2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c + .s1-2.s2, .c.s1-1 + .s1-2.s2;\n}\n" +); +grass_test!( + combinator_sibling_and_next_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c.s2-1 + .s2-2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c.s2-1 + .s1-2.s2-2, .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_sibling_and_next_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 ~ .s1-2\", \"#s2-1 + .s2-2\");\n}\n", + "a {\n color: #s1-1 ~ #s2-1 + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_descendant, + "a {\n color: selector-unify(\".c + .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .c + .s1.s2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_sibling_distinct, + "a {\n color: selector-unify(\".c + .d\", \".e ~ .f\");\n}\n", + "a {\n color: .e ~ .c + .d.f, .e.c + .d.f;\n}\n" +); +grass_test!( + combinator_next_sibling_and_sibling_identical, + "a {\n color: selector-unify(\".c + .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c + .s1.s2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c ~ .s2\");\n}\n", + "a {\n color: .c.s1-1 + .s1-2.s2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c.s2-1 ~ .s2-2\");\n}\n", + "a {\n color: .c.s2-1 ~ .c.s1-1 + .s1-2.s2-2, .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 + .s1-2\", \"#s2-1 ~ .s2-2\");\n}\n", + "a {\n color: #s2-1 ~ #s1-1 + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_next_sibling_distinct, + "a {\n color: selector-unify(\".c + .d\", \".e + .f\");\n}\n", + "a {\n color: .e.c + .d.f;\n}\n" +); +grass_test!( + combinator_next_sibling_and_next_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c + .s2\");\n}\n", + "a {\n color: .c.s1-1 + .s1-2.s2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_next_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c.s2-1 + .s2-2\");\n}\n", + "a {\n color: .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +grass_test!( + combinator_next_sibling_and_next_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 + .s1-2\", \"#s2-1 + .s2-2\");\n}\n", + "" +); +grass_test!( + combinator_at_start_first, + "a {\n color: selector-unify(\"> .c\", \".d\");\n}\n", + "a {\n color: > .c.d;\n}\n" +); +grass_test!( + combinator_at_start_second, + "a {\n color: selector-unify(\".c\", \"~ .d\");\n}\n", + "a {\n color: ~ .c.d;\n}\n" +); +grass_test!( + combinator_at_start_both_identical, + "a {\n color: selector-unify(\"+ .c\", \"+ .d\");\n}\n", + "a {\n color: + .c.d;\n}\n" +); +grass_test!( + combinator_at_start_contiguous_super_sequence, + "a {\n color: selector-unify(\"+ ~ > .c\", \"> + ~ > > .d\");\n}\n", + "a {\n color: > + ~ > > .c.d;\n}\n" +); +grass_test!( + combinator_at_start_non_contiguous_super_sequence, + "a {\n color: selector-unify(\"+ ~ > .c\", \"+ > ~ ~ > .d\");\n}\n", + "a {\n color: + > ~ ~ > .c.d;\n}\n" +); +grass_test!( + combinator_at_start_distinct, + "a {\n color: selector-unify(\"+ ~ > .c\", \"+ > ~ ~ .d\");\n}\n", + "" +); +grass_test!( + combinator_multiple, + "a {\n color: selector-unify(\".c > .d + .e\", \".f .g ~ .h\");\n}\n", + "a {\n color: .f .c > .g ~ .d + .e.h, .f .c > .g.d + .e.h;\n}\n" +); +grass_test!( + combinator_multiple_in_a_row_same, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + ~ > .f\");\n}\n", + "a {\n color: .c .e + ~ > .d.f, .e .c + ~ > .d.f;\n}\n" +); +grass_test!( + combinator_multiple_in_a_row_contiguous_super_sequence, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e > + ~ > > .f\");\n}\n", + "a {\n color: .c .e > + ~ > > .d.f, .e .c > + ~ > > .d.f;\n}\n" +); +grass_test!( + combinator_multiple_in_a_row_non_contiguous_super_sequence, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + > ~ ~ > .f\");\n}\n", + "a {\n color: .c .e + > ~ ~ > .d.f, .e .c + > ~ ~ > .d.f;\n}\n" +); +grass_test!( + combinator_multiple_in_a_row_distinct, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + > ~ ~ .f\");\n}\n", + "" +); +grass_test!( + lcs_two_vs_one, + "a {\n color: selector-unify(\".c .d .e .s1\", \".e .c .d .s2\");\n}\n", + "a {\n color: .e .c .d .e .s1.s2;\n}\n" +); +grass_test!( + lcs_three_vs_two, + "a {\n color: selector-unify(\".c .d .e .f .g .s1\", \".f .g .c .d .e .s2\");\n}\n", + "a {\n color: .f .g .c .d .e .f .g .s1.s2;\n}\n" +); +grass_test!( + lcs_non_contiguous_same_position, + "a {\n color: selector-unify(\".s1-1 .c .d .s1-2 .e .s1-3\", \".s2-1 .c .d .s2-2 .e \ + .s2-3\");\n}\n", + "a {\n color: .s1-1 .s2-1 .c .d .s1-2 .s2-2 .e .s1-3.s2-3, .s2-1 .s1-1 .c .d .s1-2 .s2-2 .e \ + .s1-3.s2-3, .s1-1 .s2-1 .c .d .s2-2 .s1-2 .e .s1-3.s2-3, .s2-1 .s1-1 .c .d .s2-2 .s1-2 .e \ + .s1-3.s2-3;\n}\n" +); +grass_test!( + lcs_non_contiguous_different_positions, + "a {\n color: selector-unify(\".s1-1 .c .d .s1-2 .e .s1-3\", \".c .s2-1 .d .e .s2-2 \ + .s2-3\");\n}\n", + "a {\n color: .s1-1 .c .s2-1 .d .s1-2 .e .s2-2 .s1-3.s2-3;\n}\n" +); +grass_test!( + root_in_first_two_layers, + "a {\n color: selector-unify(\":root .c\", \".d .e\");\n}\n", + "a {\n color: :root .d .c.e;\n}\n" +); +grass_test!( + #[ignore = "https://github.com/sass/dart-sass/issues/969"] + root_in_first_three_layers, + "a {\n color: selector-unify(\":root .c .d\", \".e .f\");\n}\n", + "a {\n color: :root .c .e .d.f, :root .e .c .d.f;\n}\n" +); +grass_test!( + root_in_second_two_layers, + "a {\n color: selector-unify(\".c .d\", \":root .e\");\n}\n", + "a {\n color: :root .c .d.e;\n}\n" +); +grass_test!( + #[ignore = "https://github.com/sass/dart-sass/issues/969"] + root_in_second_three_layers, + "a {\n color: selector-unify(\".c .d\", \":root .e .f\");\n}\n", + "a {\n color: :root .c .e .d.f, :root .e .c .d.f;\n}\n" +); +grass_test!( + root_in_both_cant_unify, + "a {\n color: selector-unify(\"c:root .d\", \"e:root .f\");\n}\n", + "" +); +grass_test!( + root_in_both_super_selector, + "a {\n color: selector-unify(\"c:root .d\", \":root .e\");\n}\n", + "a {\n color: c:root .d.e;\n}\n" +); +grass_test!( + root_in_both_can_unify, + "a {\n color: selector-unify(\".c:root .d\", \".e:root .f\");\n}\n", + "a {\n color: .e.c:root .d.f;\n}\n" +); +grass_error!( + parent_in_first_arg, + "a {\n color: selector-unify(\"&\", \"c\");\n}\n", + "Error: $selector1: Parent selectors aren't allowed here." +); +grass_error!( + parent_in_second_arg, + "a {\n color: selector-unify(\"c\", \"&\");\n}\n", + "Error: $selector2: Parent selectors aren't allowed here." +); +grass_error!( + #[ignore = "we don't include the name of the arg in the error message"] + malformed_selector_in_first_arg, + "a {\n color: selector-unify(\"[c\", \"c\");\n}\n", + "Error: $selector1: expected more input." +); +grass_error!( + #[ignore = "we don't include the name of the arg in the error message"] + malformed_selector_in_second_arg, + "a {\n color: selector-unify(\"c\", \"[c\");\n}\n", + "Error: $selector2: expected more input." +); +grass_error!( + invalid_type_in_first_arg, + "a {\n color: selector-unify(1, \"c\");\n}\n", + "Error: $selector1: 1 is not a valid selector: it must be a string, a list of strings, or a \ + list of lists of strings." +); +grass_error!( + invalid_type_in_second_arg, + "a {\n color: selector-unify(\"c\", 1);\n}\n", + "Error: $selector2: 1 is not a valid selector: it must be a string, a list of strings, or a \ + list of lists of strings." +); +grass_test!( + simple_pseudo_no_arg_class_same, + "a {\n color: selector-unify(\":c\", \":c\");\n}\n", + "a {\n color: :c;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_class_different, + "a {\n color: selector-unify(\":c\", \":d\");\n}\n", + "a {\n color: :c:d;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_same, + "a {\n color: selector-unify(\"::c\", \"::c\");\n}\n", + "a {\n color: ::c;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_different, + "a {\n color: inspect(selector-unify(\"::c\", \"::d\"));\n}\n", + "a {\n color: null;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_and_class_same_before, + "a {\n color: selector-unify(\":before\", \"::before\");\n}\n", + "a {\n color: :before;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_and_class_same_after, + "a {\n color: selector-unify(\":after\", \"::after\");\n}\n", + "a {\n color: :after;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_and_class_same_first_line, + "a {\n color: selector-unify(\":first-line\", \"::first-line\");\n}\n", + "a {\n color: :first-line;\n}\n" +); +grass_test!( + simple_pseudo_no_arg_element_and_class_same_first_letter, + "a {\n color: selector-unify(\":first-letter\", \"::first-letter\");\n}\n", + "a {\n color: :first-letter;\n}\n" +); +grass_test!( + simple_pseudo_arg_class_same, + "a {\n color: selector-unify(\":c(@#$)\", \":c(@#$)\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_arg_class_different_arg, + "a {\n color: selector-unify(\":c(@#$)\", \":c(*&^)\");\n}\n", + "a {\n color: :c(@#$):c(*&^);\n}\n" +); +grass_test!( + simple_pseudo_arg_element_same, + "a {\n color: selector-unify(\"::c(@#$)\", \"::c(@#$)\");\n}\n", + "a {\n color: ::c(@#$);\n}\n" +); +grass_test!( + simple_pseudo_arg_element_different_arg, + "a {\n color: inspect(selector-unify(\"::c(@#$)\", \"::c(*&^)\"));\n}\n", + "a {\n color: null;\n}\n" +); +grass_test!( + simple_pseudo_arg_matches_same_selector_arg, + "a {\n color: selector-unify(\":matches(.c)\", \":matches(.c)\");\n}\n", + "a {\n color: :matches(.c);\n}\n" +); +grass_test!( + simple_pseudo_arg_matches_different_selector_arg, + "a {\n color: selector-unify(\":matches(.c)\", \":matches(.d)\");\n}\n", + "a {\n color: :matches(.c):matches(.d);\n}\n" +); diff --git a/css/parser/tests/grass_selectors.rs b/css/parser/tests/grass_selectors.rs new file mode 100644 index 000000000000..d68315bf2e36 --- /dev/null +++ b/css/parser/tests/grass_selectors.rs @@ -0,0 +1,742 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + selector_nesting_el_mul_el, + "a, b {\n a, b {\n color: red\n}\n}\n", + "a a, a b, b a, b b {\n color: red;\n}\n" +); +grass_test!(selector_element, "a {\n color: red;\n}\n"); +grass_test!(selector_id, "#id {\n color: red;\n}\n"); +grass_test!(selector_class, ".class {\n color: red;\n}\n"); +grass_test!(selector_el_descendant, "a a {\n color: red;\n}\n"); +grass_test!(selector_universal, "* {\n color: red;\n}\n"); +grass_test!(selector_el_class_and, "a.class {\n color: red;\n}\n"); +grass_test!(selector_el_id_and, "a#class {\n color: red;\n}\n"); +grass_test!( + selector_el_class_descendant, + "a .class {\n color: red;\n}\n" +); +grass_test!(selector_el_id_descendant, "a #class {\n color: red;\n}\n"); +grass_test!( + selector_el_universal_descendant, + "a * {\n color: red;\n}\n" +); +grass_test!( + selector_universal_el_descendant, + "* a {\n color: red;\n}\n" +); + +grass_test!(selector_attribute_any, "[attr] {\n color: red;\n}\n"); +grass_test!( + selector_attribute_any_lower_case_insensitive, + "[attr=val i] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_interpolate, + "[a#{tt}r=v#{a}l] {\n color: red;\n}\n", + "[attr=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_any_upper_case_insensitive, + "[attr=val I] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_arbitrary_modifier, + "[attr=val c] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_i_in_attr, + "[atitr=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_i_in_val, + "[attr=vail] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_whitespace, + "[attr *= val ] {\n color: red;\n}\n", + "[attr*=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_equals, + "[attr=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_removes_single_quotes, + "[attr='val'] {\n color: red;\n}\n", + "[attr=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_removes_double_quotes, + "[attr=\"val\"] {\n color: red;\n}\n", + "[attr=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_quotes_non_ident, + "[attr=\"1\"] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_quotes_custom_property, + "[attr=\"--foo\"] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_unquoted_escape, + "[attr=v\\al] {\n color: red;\n}\n", + "[attr=v\\a l] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_quoted_escape, + "[attr=\"v\\al\"] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_namespace, + "[*|foo] {\n color: red;\n}\n" +); +grass_error!( + selector_attribute_missing_equal, + "[a~b] {\n color: red;\n}\n", + "Error: expected \"=\"." +); +grass_test!( + selector_attribute_maintains_quotes_around_invalid_identifier, + "[attr=\"val.\"] {\n color: red;\n}\n" +); +grass_error!( + attribute_attr_quoted, + "[\"attr\"=val] {\n color: red;\n}\n", + "Error: Expected identifier." +); +grass_test!(selector_attribute_in, "[attr~=val] {\n color: red;\n}\n"); +grass_test!( + selector_attribute_begins_hyphen_or_exact, + "[attr|=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_starts_with, + "[attr^=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_ends_with, + "[attr$=val] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_contains, + "[attr*=val] {\n color: red;\n}\n" +); +grass_test!(selector_el_attribute_and, "a[attr] {\n color: red;\n}\n"); +grass_test!( + selector_el_attribute_descendant, + "a [attr] {\n color: red;\n}\n" +); +grass_test!( + selector_attribute_interpolated, + "a {\n [#{&}] {\n color: red;\n }\n}\n", + "a [a] {\n color: red;\n}\n" +); +grass_test!(selector_el_mul_el, "a, b {\n color: red;\n}\n"); +grass_test!( + selector_el_immediate_child_el, + "a > b {\n color: red;\n}\n" +); +grass_test!(selector_el_following_el, "a + b {\n color: red;\n}\n"); +grass_test!(selector_el_preceding_el, "a ~ b {\n color: red;\n}\n"); +grass_test!(selector_pseudo, ":pseudo {\n color: red;\n}\n"); +grass_test!(selector_el_and_pseudo, "a:pseudo {\n color: red;\n}\n"); +grass_test!( + selector_el_pseudo_descendant, + "a :pseudo {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_el_descendant, + ":pseudo a {\n color: red;\n}\n" +); +grass_test!(selector_pseudoelement, "::before {\n color: red;\n}\n"); +grass_test!( + selector_el_and_pseudoelement, + "a::before {\n color: red;\n}\n" +); +grass_test!( + selector_el_pseudoelement_descendant, + "a ::before {\n color: red;\n}\n" +); +grass_test!( + selector_pseudoelement_el_descendant, + "::before a {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_paren_comma, + ":pseudo(a, b, c) {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_paren_space, + ":pseudo(a b c) {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_paren_whitespacespace, + ":pseudo( -2n+1 ) {\n color: red;\n}\n", + ":pseudo(-2n+1) {\n color: red;\n}\n" +); +grass_test!( + selector_el_pseudo_paren_and, + "a:pseudo(a, b, c) {\n color: red;\n}\n" +); +grass_test!( + selector_el_pseudo_paren_descendant, + "a :pseudo(a, b, c) {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_paren_el_descendant, + ":pseudo(a, b, c) a {\n color: red;\n}\n" +); +grass_test!( + selector_pseudo_paren_el_nested, + "a {\n :pseudo(a, b, c) {\n color: red;\n }\n}\n", + "a :pseudo(a, b, c) {\n color: red;\n}\n" +); +grass_test!(selector_mul, "a, b {\n color: red;\n}\n"); +grass_test!( + outer_ampersand, + "a, b {\n& c {\n color: red;\n}\n}\n", + "a c, b c {\n color: red;\n}\n" +); +grass_test!( + inner_ampersand, + "a, b {\na & c {\n color: red;\n}\n}\n", + "a a c, a b c {\n color: red;\n}\n" +); +grass_test!( + ampersand_multiple_whitespace, + " a , b {\n&c {\n color: red;\n}\n}\n", + "ac, bc {\n color: red;\n}\n" +); +grass_test!( + ampersand_alone, + "a, b {\n& {\n color: red;\n}\n}\n", + "a, b {\n color: red;\n}\n" +); +grass_test!( + bem_dash_dash_selector, + "a {\n&--b {\n color: red;\n}\n}\n", + "a--b {\n color: red;\n}\n" +); +grass_test!( + bem_underscore_selector, + "a {\n&__b {\n color: red;\n}\n}\n", + "a__b {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_addition, + "#{\"foo\" + \" bar\"}baz {color: red;}", + "foo barbaz {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_start, + "#{a}bc {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_middle, + "a#{b}c {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_end, + "ab#{c} {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_variable, + "$a: foo;\nab#{$a} {\n color: red;\n}\n", + "abfoo {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_super_selector, + "a {\nb #{&} { color: red; }}", + "a b a {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_super_selector_root_postfix, + "a#{&} {\nb { color: red; }}", + "a b {\n color: red;\n}\n" +); +grass_test!( + selector_interpolation_super_selector_root_prefix, + "#{&}a {\nb { color: red; }}", + "a b {\n color: red;\n}\n" +); +grass_test!( + selector_whitespace, + " a > b , c ~ d e .f #g :h i.j [ k ] { color: red }", + "a > b, c ~ d e .f #g :h i.j [k] {\n color: red;\n}\n" +); +grass_test!( + comment_between_selectors, + "a /* foo */ b {\n color: red;\n}\n", + "a b {\n color: red;\n}\n" +); +grass_test!( + interpolates_comma, + "$x: oo, ba;\nf#{$x}r {\n baz {\n color: red;\n }\n}\n", + "foo baz, bar baz {\n color: red;\n}\n" +); +grass_test!( + extra_commas, + "div,, , span, ,, {\n color: red;\n}\n", + "div, span {\n color: red;\n}\n" +); +grass_test!( + combinator_following, + "a + {\n b {\n color: red;\n }\n}\n", + "a + b {\n color: red;\n}\n" +); +grass_test!( + combinator_preceding, + "a {\n + b {\n color: red;\n }\n}\n", + "a + b {\n color: red;\n}\n" +); +grass_test!( + combinator_alone, + "a {\n + {\n b {\n color: red;\n }\n}\n", + "a + b {\n color: red;\n}\n" +); +grass_test!(simple_multiple_newline, "a,\nb {\n color: red;\n}\n"); +grass_test!( + nested_multiple_newline, + "a,\nb {\n c {\n color: blue;\n }\n color: red;\n}\n", + "a,\nb {\n color: red;\n}\na c,\nb c {\n color: blue;\n}\n" +); +grass_test!( + trailing_comma_newline, + "#foo #bar,,\n,#baz #boom, {a: b}", + "#foo #bar,\n#baz #boom {\n a: b;\n}\n" +); +grass_test!( + trailing_comma_children, + "a,, {\n b {\n color: /**/red;\n }\n}\n", + "a b {\n color: red;\n}\n" +); +grass_test!(simple_placeholder, "%a {\n color: red;\n}\n", ""); +grass_test!( + placeholder_first, + "%a, b {\n color: red;\n}\n", + "b {\n color: red;\n}\n" +); +grass_test!( + placeholder_last, + "a, %b {\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + placeholder_middle, + "a, %b, c {\n color: red;\n}\n", + "a, c {\n color: red;\n}\n" +); +grass_test!( + removes_leading_space, + "#{&} a {\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!(allows_id_start_with_number, "#2foo {\n color: red;\n}\n"); +grass_test!(allows_id_only_number, "#2 {\n color: red;\n}\n"); +grass_test!( + id_interpolation, + "$zzz: zzz;\n##{$zzz} {\n a: b;\n}\n", + "#zzz {\n a: b;\n}\n" +); +grass_test!( + interpolate_id_selector, + "$bar: \"#foo\";\nul li#{$bar} {\n foo: bar;\n}\n", + "ul li#foo {\n foo: bar;\n}\n" +); +grass_test!(escaped_space, "a\\ b {\n color: foo;\n}\n"); +grass_test!( + escaped_bang, + "\\! {\n color: red;\n}\n", + "\\! {\n color: red;\n}\n" +); +grass_test!( + multiple_consecutive_immediate_child, + "> > foo {\n color: foo;\n}\n" +); +grass_error!( + modifier_on_any_attr, + "[attr i] {color: foo;}", + "Error: Expected \"]\"." +); +grass_test!( + psuedo_paren_child_contains_ampersand, + ".a, .b {\n :not(&-c) {\n d: e\n }\n}\n", + ":not(.a-c, .b-c) {\n d: e;\n}\n" +); +grass_test!( + psuedo_paren_child_no_ampersand_two_newlines__this_test_confounds_me, + ".a, .b {\n :not(-c),\n :not(-c) {\n d: e\n }\n}\n", + ".a :not(-c),\n.a :not(-c), .b :not(-c),\n.b :not(-c) {\n d: e;\n}\n" +); +grass_test!( + psuedo_paren_child_ampersand_two_newlines__this_test_confounds_me, + ".a, .b {\n :not(&-c, &-d),\n :not(&-c) {\n d: e\n }\n}\n", + ":not(.a-c, .a-d, .b-c, .b-d), :not(.a-c, .b-c) {\n d: e;\n}\n" +); +grass_test!( + psuedo_paren_child_ampersand_inner_psuedo_paren, + ".a, .b {\n :not(:not(&-c)) {\n d: e\n }\n}\n", + ":not(:not(.a-c, .b-c)) {\n d: e;\n}\n" +); +grass_test!( + psuedo_paren_child_psuedo_paren_ampersand_inner_psuedo_paren, + ".a, .b {\n :not(:not(c), &-d) {\n d: e\n }\n}\n", + ":not(:not(c), .a-d, .b-d) {\n d: e;\n}\n" +); +grass_test!( + psuedo_paren_child_ampersand_psuedo_paren__inner_psuedo_paren, + ".a, .b {\n :not(&-d, :not(c)) {\n d: e\n }\n}\n", + ":not(.a-d, :not(c), .b-d) {\n d: e;\n}\n" +); +grass_test!( + sass_spec__nesting_parent_with_newline, + ".foo,\n.bar {\n .baz & {\n color: red;\n }\n}\n", + ".baz .foo,\n.baz .bar {\n color: red;\n}\n" +); +grass_test!( + not_only_placeholder_is_universal, + "a :not(%b) {x: y}", + "a * {\n x: y;\n}\n" +); +grass_test!( + not_placeholder_is_removed, + "a:not(%b, c) {x: y}", + "a:not(c) {\n x: y;\n}\n" +); +grass_test!( + psuedo_paren_removes_inner_placeholder, + "a:matches(%b, c) {x: y}", + "a:matches(c) {\n x: y;\n}\n" +); +grass_test!( + matches_placeholder_removes_everything, + "a:matches(%b) {x: y}", + "" +); +grass_test!( + touching_universal_stays_the_same, + "a* {\n color: red;\n}\n" +); +grass_test!( + adjacent_not_placeholder_is_ignored, + "a:not(%b) {x: y}", + "a {\n x: y;\n}\n" +); +grass_test!( + pseudo_paren_placeholder_alone, + ":not(%b) {x: y}", + "* {\n x: y;\n}\n" +); +grass_test!( + interpolated_super_selector_in_style, + "a {\n color: #{&};\n}\n", + "a {\n color: a;\n}\n" +); +grass_test!( + interpolated_super_selector_in_style_symbols, + "* .a #b:foo() {\n color: #{&};\n}\n", + "* .a #b:foo() {\n color: * .a #b:foo();\n}\n" +); +grass_test!( + uninterpolated_super_selector, + "* .a #b:foo() {\n color: &;\n}\n", + "* .a #b:foo() {\n color: * .a #b:foo();\n}\n" +); +grass_test!( + interpolated_super_selector_in_selector_and_style, + "a {\n b #{&} {\n color: &;\n }\n}\n", + "a b a {\n color: a b a;\n}\n" +); +grass_test!( + super_selector_treated_as_list, + "a, b {\n color: type-of(&);\n}\n", + "a, b {\n color: list;\n}\n" +); +grass_test!( + length_of_comma_separated_super_selector, + "a, b {\n color: length(&);\n}\n", + "a, b {\n color: 2;\n}\n" +); +grass_test!( + nth_1_of_comma_separated_super_selector, + "a, b {\n color: nth(&, 1);\n}\n", + "a, b {\n color: a;\n}\n" +); +grass_test!( + nth_2_of_comma_separated_super_selector, + "a, b {\n color: nth(&, 2);\n}\n", + "a, b {\n color: b;\n}\n" +); +grass_test!( + length_of_space_separated_super_selector, + "a b {\n color: length(&);\n}\n", + "a b {\n color: 1;\n}\n" +); +grass_test!( + nth_1_of_space_separated_super_selector, + "a b {\n color: nth(&, 1);\n}\n", + "a b {\n color: a b;\n}\n" +); +grass_test!( + length_of_comma_separated_super_selector_has_compound, + "a:foo, b {\n color: length(&);\n}\n", + "a:foo, b {\n color: 2;\n}\n" +); +grass_test!( + nth_1_of_comma_separated_super_selector_has_compound, + "a:foo, b {\n color: nth(&, 1);\n}\n", + "a:foo, b {\n color: a:foo;\n}\n" +); +grass_test!( + length_of_space_separated_super_selector_has_compound, + "a:foo b {\n color: length(&);\n}\n", + "a:foo b {\n color: 1;\n}\n" +); +grass_test!( + nth_1_of_space_separated_super_selector_has_compound, + "a:foo b {\n color: nth(&, 1);\n}\n", + "a:foo b {\n color: a:foo b;\n}\n" +); +grass_test!( + length_super_selector_placeholder, + "a, %b {\n color: length(&);\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + nth_2_super_selector_placeholder, + "a, %b {\n color: nth(&, 2);\n}\n", + "a {\n color: %b;\n}\n" +); +grass_test!( + escape_at_start_of_selector, + "\\61 {\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); +// this checks for a bug in which the selector +// contents get longer as a result of interpolation, +// which interferes with span information. +grass_test!( + selector_span_gets_larger, + "$a: aaaaaaaaaaa;\n\n#{$a} {\n color: foo;\n}\n", + "aaaaaaaaaaa {\n color: foo;\n}\n" +); +grass_test!( + toplevel_non_ascii_alphabetic, + "ℓ {\n color: red;\n}\n", + "@charset \"UTF-8\";\nℓ {\n color: red;\n}\n" +); +grass_test!( + plus_in_selector, + "+ {\n color: &;\n}\n", + "+ {\n color: +;\n}\n" +); +grass_test!( + invalid_chars_in_pseudo_parens, + ":c(@#$) {\n color: &;\n}\n", + ":c(@#$) {\n color: :c(@#$);\n}\n" +); +grass_test!( + empty_namespace_element, + "|f {\n color: &;\n}\n", + "|f {\n color: |f;\n}\n" +); +grass_test!( + universal_with_namespace, + "a|* {\n color: &;\n}\n", + "a|* {\n color: a|*;\n}\n" +); +grass_test!( + psuedo_element_slotted_args, + "::slotted(b, c) {\n color: &;\n}\n", + "::slotted(b, c) {\n color: ::slotted(b, c);\n}\n" +); +grass_test!( + a_n_plus_b, + ":nth-child(2n+0) {\n color: &;\n}\n", + ":nth-child(2n+0) {\n color: :nth-child(2n+0);\n}\n" +); +grass_test!( + a_n_plus_b_leading_negative, + ":nth-child(-1n+6) {\n color: &;\n}\n", + ":nth-child(-1n+6) {\n color: :nth-child(-1n+6);\n}\n" +); +grass_test!( + a_n_plus_b_leading_plus, + ":nth-child(+3n-2) {\n color: &;\n}\n", + ":nth-child(+3n-2) {\n color: :nth-child(+3n-2);\n}\n" +); +grass_test!( + a_n_plus_b_n_alone, + ":nth-child(n) {\n color: &;\n}\n", + ":nth-child(n) {\n color: :nth-child(n);\n}\n" +); +grass_test!( + a_n_plus_b_capital_n, + ":nth-child(N) {\n color: &;\n}\n", + ":nth-child(n) {\n color: :nth-child(n);\n}\n" +); +grass_test!( + a_n_plus_b_n_with_leading_number, + ":nth-child(2n) {\n color: &;\n}\n", + ":nth-child(2n) {\n color: :nth-child(2n);\n}\n" +); +grass_test!( + a_n_plus_b_n_whitespace_on_both_sides, + ":nth-child(3n + 1) {\n color: &;\n}\n", + ":nth-child(3n+1) {\n color: :nth-child(3n+1);\n}\n" +); +grass_test!( + a_n_plus_b_n_of, + ":nth-child(2n+1 of b, c) {\n color: &;\n}\n", + ":nth-child(2n+1 of b, c) {\n color: :nth-child(2n+1 of b, c);\n}\n" +); +grass_test!( + a_n_plus_b_n_number_alone, + ":nth-child(5) {\n color: &;\n}\n", + ":nth-child(5) {\n color: :nth-child(5);\n}\n" +); +grass_test!( + a_n_plus_b_n_number_leading_negative, + ":nth-child(-5) {\n color: &;\n}\n", + ":nth-child(-5) {\n color: :nth-child(-5);\n}\n" +); +grass_test!( + a_n_plus_b_n_number_leading_plus, + ":nth-child(+5) {\n color: &;\n}\n", + ":nth-child(+5) {\n color: :nth-child(+5);\n}\n" +); +grass_test!( + a_n_plus_b_n_leading_negative_no_leading_number, + ":nth-child(-n+ 6) {\n color: &;\n}\n", + ":nth-child(-n+6) {\n color: :nth-child(-n+6);\n}\n" +); +grass_test!( + a_n_plus_b_n_even_all_lowercase, + ":nth-child(even) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +grass_test!( + a_n_plus_b_n_even_mixed_case, + ":nth-child(eVeN) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +grass_test!( + a_n_plus_b_n_even_uppercase, + ":nth-child(EVEN) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +grass_test!( + a_n_plus_b_n_even_whitespace, + ":nth-child( even ) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +grass_error!( + a_n_plus_b_n_value_after_even, + ":nth-child(even 1) {\n color: &;\n}\n", + "Error: Expected \"of\"." +); +grass_error!( + a_n_plus_b_n_invalid_even, + ":nth-child(efven) {\n color: &;\n}\n", + "Error: Expected \"even\"." +); +grass_test!( + a_n_plus_b_n_odd_all_lowercase, + ":nth-child(odd) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +grass_test!( + a_n_plus_b_n_odd_mixed_case, + ":nth-child(oDd) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +grass_test!( + a_n_plus_b_n_odd_uppercase, + ":nth-child(ODD) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +grass_test!( + escaped_space_at_end_of_selector_immediately_after_pseudo_color, + "a color:\\ {\n color: &;\n}\n", + "a color:\\ {\n color: a color:\\ ;\n}\n" +); +grass_test!( + super_selector_is_null_when_at_root, + "@mixin foo {\n #{if(&, 'true', 'false')} {\n color: red;\n }\n}\n\n@include \ + foo;\n\na {\n @include foo;\n}\n", + "false {\n color: red;\n}\n\na true {\n color: red;\n}\n" +); +grass_test!( + newline_is_preserved_when_following_comment, + "a, // 1\nb,\nc {\n color: red;\n}\n", + "a,\nb,\nc {\n color: red;\n}\n" +); +grass_test!( + spaces_are_preserved_before_comma_in_pseudo_arg, + ":a(a , b) {\n color: &;\n}\n", + ":a(a , b) {\n color: :a(a , b);\n}\n" +); +grass_test!( + parent_selector_is_null_at_root, + "#{inspect(&)} {\n color: &;\n}\n", + "null {\n color: null;\n}\n" +); +grass_test!( + #[ignore = "we do not yet have a good way of consuming a string without converting \\a to a \ + newline"] + silent_comment_in_quoted_attribute_value, + ".foo bar[val=\"//\"] {\n color: &;\n}\n", + ".foo bar[val=\"//\"] {\n color: .foo bar[val=\"//\"];\n}\n" +); +grass_error!( + a_n_plus_b_n_invalid_odd, + ":nth-child(ofdd) {\n color: &;\n}\n", + "Error: Expected \"odd\"." +); +grass_error!( + a_n_plus_b_n_invalid_starting_char, + ":nth-child(f) {\n color: &;\n}\n", + "Error: Expected \"n\"." +); +grass_error!( + a_n_plus_b_n_nothing_after_open_paren, + ":nth-child({\n color: &;\n}\n", + "Error: expected more input." +); +grass_error!( + a_n_plus_b_n_invalid_char_after_even, + ":nth-child(even#) {\n color: &;\n}\n", + "Error: expected \")\"." +); +grass_error!(nothing_after_period, ". {}", "Error: Expected identifier."); +grass_error!(nothing_after_hash, "# {}", "Error: Expected identifier."); +grass_error!(nothing_after_percent, "% {}", "Error: Expected identifier."); +grass_error!(no_ident_after_colon, ": {}", "Error: Expected identifier."); +grass_error!(double_colon_no_space, "::{}", "Error: Expected identifier."); +grass_error!( + non_ident_char_after_colon, + ":#ab {}", + "Error: Expected identifier." +); +grass_error!(nothing_after_colon, "a:{}", "Error: Expected identifier."); +grass_error!( + toplevel_parent_selector_after_combinator, + "~&{}", + "Error: Top-level selectors may not contain the parent selector \"&\"." +); +grass_error!( + toplevel_parent_selector_after_element, + "a&{}", + "Error: \"&\" may only used at the beginning of a compound selector." +); +grass_error!( + denies_optional_in_selector, + "a !optional {}", + "Error: expected \"{\"." +); diff --git a/css/parser/tests/grass_simple_selectors.rs b/css/parser/tests/grass_simple_selectors.rs new file mode 100644 index 000000000000..36554d9ad196 --- /dev/null +++ b/css/parser/tests/grass_simple_selectors.rs @@ -0,0 +1,13 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + two_classes, + "a {\n color: simple-selectors(\".foo.bar\");\n}\n", + "a {\n color: .foo, .bar;\n}\n" +); +grass_test!( + three_classes, + "a {\n color: simple-selectors(\".foo.bar.baz\");\n}\n", + "a {\n color: .foo, .bar, .baz;\n}\n" +); diff --git a/css/parser/tests/grass_special_functions.rs b/css/parser/tests/grass_special_functions.rs new file mode 100644 index 000000000000..947b0b96a06f --- /dev/null +++ b/css/parser/tests/grass_special_functions.rs @@ -0,0 +1,210 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + calc_whitespace, + "a {\n color: calc( 1 );\n}\n", + "a {\n color: calc( 1 );\n}\n" +); +grass_test!( + calc_newline, + "a {\n color: calc(\n);\n}\n", + "a {\n color: calc( );\n}\n" +); +grass_test!( + calc_multiple_args, + "a {\n color: calc(1, 2, a, b, c);\n}\n" +); +grass_test!( + calc_does_not_evaluate_arithmetic, + "a {\n color: calc(1 + 2);\n}\n" +); +grass_test!( + calc_evaluates_interpolated_arithmetic, + "a {\n color: calc(#{1 + 2});\n}\n", + "a {\n color: calc(3);\n}\n" +); +grass_test!(calc_retains_silent_comment, "a {\n color: calc(//);\n}\n"); +grass_test!( + calc_retains_multiline_comment, + "a {\n color: calc(/**/);\n}\n" +); +grass_test!(calc_nested_parens, "a {\n color: calc((((()))));\n}\n"); +grass_test!( + calc_invalid_arithmetic, + "a {\n color: calc(2px + 2px + 5%);\n}\n" +); +grass_test!( + calc_uppercase, + "a {\n color: CALC(1 + 1);\n}\n", + "a {\n color: calc(1 + 1);\n}\n" +); +grass_test!( + calc_mixed_casing, + "a {\n color: cAlC(1 + 1);\n}\n", + "a {\n color: calc(1 + 1);\n}\n" +); +grass_test!( + calc_browser_prefixed, + "a {\n color: -webkit-calc(1 + 2);\n}\n", + "a {\n color: -webkit-calc(1 + 2);\n}\n" +); +grass_test!( + calc_quoted_string, + r#"a { color: calc("\ "); }"#, + "a {\n color: calc(\" \");\n}\n" +); +grass_test!( + calc_quoted_string_single_quoted_paren, + "a {\n color: calc(\")\");\n}\n", + "a {\n color: calc(\")\");\n}\n" +); +grass_test!( + calc_quoted_string_single_quotes, + "a {\n color: calc('a');\n}\n", + "a {\n color: calc(\"a\");\n}\n" +); +grass_test!( + calc_hash_no_interpolation, + "a {\n color: calc(#);\n}\n", + "a {\n color: calc(#);\n}\n" +); +grass_test!( + element_whitespace, + "a {\n color: element( 1 );\n}\n", + "a {\n color: element( 1 );\n}\n" +); +grass_test!( + element_newline, + "a {\n color: element(\n);\n}\n", + "a {\n color: element( );\n}\n" +); +grass_test!( + element_multiple_args, + "a {\n color: element(1, 2, a, b, c);\n}\n" +); +grass_test!( + element_does_not_evaluate_arithmetic, + "a {\n color: element(1 + 2);\n}\n" +); +grass_test!( + element_evaluates_interpolated_arithmetic, + "a {\n color: element(#{1 + 2});\n}\n", + "a {\n color: element(3);\n}\n" +); +grass_test!( + element_retains_silent_comment, + "a {\n color: element(//);\n}\n" +); +grass_test!( + element_retains_multiline_comment, + "a {\n color: element(/**/);\n}\n" +); +grass_test!( + element_nested_parens, + "a {\n color: element((((()))));\n}\n" +); +grass_test!( + element_browser_prefixed, + "a {\n color: -webkit-element(1 + 2);\n}\n", + "a {\n color: -webkit-element(1 + 2);\n}\n" +); +grass_test!( + expression_whitespace, + "a {\n color: expression( 1 );\n}\n", + "a {\n color: expression( 1 );\n}\n" +); +grass_test!( + expression_newline, + "a {\n color: expression(\n);\n}\n", + "a {\n color: expression( );\n}\n" +); +grass_test!( + expression_multiple_args, + "a {\n color: expression(1, 2, a, b, c);\n}\n" +); +grass_test!( + expression_does_not_evaluate_arithmetic, + "a {\n color: expression(1 + 2);\n}\n" +); +grass_test!( + expression_evaluates_interpolated_arithmetic, + "a {\n color: expression(#{1 + 2});\n}\n", + "a {\n color: expression(3);\n}\n" +); +grass_test!( + expression_retains_silent_comment, + "a {\n color: expression(//);\n}\n" +); +grass_test!( + expression_retains_multiline_comment, + "a {\n color: expression(/**/);\n}\n" +); +grass_test!( + expression_nested_parens, + "a {\n color: expression((((()))));\n}\n" +); +grass_test!( + expression_browser_prefixed, + "a {\n color: -webkit-expression(1 + 2);\n}\n", + "a {\n color: -webkit-expression(1 + 2);\n}\n" +); +grass_test!( + progid_whitespace, + "a {\n color: progid:( 1 );\n}\n", + "a {\n color: progid:( 1 );\n}\n" +); +grass_test!( + progid_newline, + "a {\n color: progid:(\n);\n}\n", + "a {\n color: progid:( );\n}\n" +); +grass_test!( + progid_multiple_args, + "a {\n color: progid:(1, 2, a, b, c);\n}\n" +); +grass_test!( + progid_does_not_evaluate_arithmetic, + "a {\n color: progid:(1 + 2);\n}\n" +); +grass_test!( + progid_evaluates_interpolated_arithmetic, + "a {\n color: progid:(#{1 + 2});\n}\n", + "a {\n color: progid:(3);\n}\n" +); +grass_test!( + progid_retains_silent_comment, + "a {\n color: progid:(//);\n}\n" +); +grass_test!( + progid_retains_multiline_comment, + "a {\n color: progid:(/**/);\n}\n" +); +grass_test!( + progid_nested_parens, + "a {\n color: progid:((((()))));\n}\n" +); +grass_test!( + progid_values_after_colon, + "a {\n color: progid:apple.bottoM..jeans.boots();\n}\n" +); +grass_error!( + progid_number_after_colon, + "a {\n color: progid:ap1ple.bottoM..jeans.boots();\n}\n", + "Error: expected \"(\"." +); +grass_test!( + progid_uppercase, + "a {\n color: PROGID:foo(fff);\n}\n", + "a {\n color: progid:foo(fff);\n}\n" +); +grass_test!( + progid_mixed_casing, + "a {\n color: PrOgId:foo(fff);\n}\n", + "a {\n color: progid:foo(fff);\n}\n" +); +grass_error!( + progid_nothing_after, + "a { color: progid:", + "Error: expected \"(\"." +); diff --git a/css/parser/tests/grass_splat.rs b/css/parser/tests/grass_splat.rs new file mode 100644 index 000000000000..796ceb07bed5 --- /dev/null +++ b/css/parser/tests/grass_splat.rs @@ -0,0 +1,74 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + splat_list_two_elements, + "@function foo($a, $b) { + @return $a+$b; + } + a { + color: foo([1, 2]...); + }", + "a {\n color: 3;\n}\n" +); +grass_test!( + splat_map_single_key, + "@function foo($a) { + @return $a; + } + a { + color: foo((a: b)...); + }", + "a {\n color: b;\n}\n" +); +grass_test!( + splat_single_value, + "@function foo($a) { + @return $a; + } + a { + color: foo(1...); + }", + "a {\n color: 1;\n}\n" +); +grass_test!( + splat_map_quoted_string_as_key, + "a { + color: red((\"color\": red)...); + }", + "a {\n color: 255;\n}\n" +); +grass_error!( + splat_missing_last_period, + "@function foo($a) { + @return $a; + } + a { + color: foo(1..); + }", + "Error: expected \".\"." +); +grass_error!( + splat_with_named_arg, + "@function foo($a) { + @return $a; + } + a { + color: foo($a: 1...); + }", + "Error: expected \")\"." +); +grass_error!( + splat_map_with_non_string_key_map, + "a { + color: red(((a: b): red)...); + }", + "Error: (a: b) is not a string in ((a: b): red)." +); +grass_error!( + splat_map_with_non_string_key_number, + "a { + color: red((1: red)...); + }", + "Error: 1 is not a string in (1: red)." +); diff --git a/css/parser/tests/grass_str_escape.rs b/css/parser/tests/grass_str_escape.rs new file mode 100644 index 000000000000..7563480093aa --- /dev/null +++ b/css/parser/tests/grass_str_escape.rs @@ -0,0 +1,177 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + escape_leading_zeros, + "a {\n color: ax \\61x \\61 x \\061x \\0061x \\00061x;\n}\n", + "a {\n color: ax ax ax ax ax ax;\n}\n" +); +grass_test!( + escape_start_non_hex, + "a {\n color: \\xx;\n}\n", + "a {\n color: xx;\n}\n" +); +grass_test!( + escape_start_non_ascii, + "a {\n color: ☃x \\☃x \\2603x;\n}\n", + "@charset \"UTF-8\";\na {\n color: ☃x ☃x ☃x;\n}\n" +); +grass_test!( + escape_hyphen_in_middle, + "a {\n color: a\\2dx a\\-x;\n}\n", + "a {\n color: a-x a-x;\n}\n" +); +grass_test!( + escape_hyphen_at_start, + "a {\n color: \\2dx \\-x;\n}\n", + "a {\n color: \\-x \\-x;\n}\n" +); +grass_test!( + escape_digit_in_middle, + "a {\n color: a\\31x a\\31 x;\n}\n", + "a {\n color: a1x a1x;\n}\n" +); +grass_test!( + escape_digit_at_start, + "a {\n color: \\31x \\31 x;\n}\n", + "a {\n color: \\31 x \\31 x;\n}\n" +); +grass_test!( + escape_non_printable_characters, + "a {\n color: \\0x \\1x \\2x \\3x \\4x \\5x \\6x \\7x \\8x \\Bx \\Ex \\Fx \\10x \\11x \\12x \ + \\13x \\14x \\15x \\16x \\17x \\18x \\19x \\1Ax \\1Bx \\1Cx \\1Dx \\1Ex \\1Fx \\7Fx;\n}\n", + "a {\n color: \\0 x \\1 x \\2 x \\3 x \\4 x \\5 x \\6 x \\7 x \\8 x \\b x \\e x \\f x \\10 x \ + \\11 x \\12 x \\13 x \\14 x \\15 x \\16 x \\17 x \\18 x \\19 x \\1a x \\1b x \\1c x \\1d x \ + \\1e x \\1f x \\7f x;\n}\n" +); +grass_test!( + escape_newlines, + "a {\n color: \\ax \\cx \\dx;\n}\n", + "a {\n color: \\a x \\c x \\d x;\n}\n" +); +grass_test!( + escape_tabs, + "a {\n color: \\ x \\9x;\n}\n", + "a {\n color: \\9 x \\9 x;\n}\n" +); +grass_test!( + escape_interpolation_start, + "a {\n color: \\-#{foo};\n}\n", + "a {\n color: \\-foo;\n}\n" +); +grass_test!( + escape_interpolation_middle, + "a {\n color: #{foo}\\-#{bar};\n}\n", + "a {\n color: foo-bar;\n}\n" +); +grass_test!( + escape_interpolation_end, + "a {\n color: #{foo}\\-;\n}\n", + "a {\n color: foo-;\n}\n" +); +grass_test!( + escape_in_middle, + "a {\n color: b\\6cue;\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + escape_at_end, + "a {\n color: blu\\65;\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!(double_escape_is_preserved, "a {\n color: r\\\\65;\n}\n"); +grass_test!(semicolon_in_string, "a {\n color: \";\";\n}\n"); +grass_test!( + single_character_escape_sequence_has_space, + "a {\n color: \\fg1;\n}\n", + "a {\n color: \\f g1;\n}\n" +); +grass_test!( + single_character_escape_sequence_removes_slash_when_not_hex_digit, + "a {\n color: \\g1;\n}\n", + "a {\n color: g1;\n}\n" +); +grass_test!( + single_character_escape_sequence_has_space_after, + "a {\n color: \\a;\n}\n", + "a {\n color: \\a ;\n}\n" +); +grass_test!( + escapes_non_hex_in_string, + "a {\n color: \"\\g\";\n}\n", + "a {\n color: \"g\";\n}\n" +); +grass_test!( + escapes_hex_in_string_no_trailing_space, + "a {\n color: \ + \"\\b\\c\\d\\e\\f\\g\\h\\i\\j\\k\\l\\m\\n\\o\\p\\q\\r\\s\\t\\u\\v\\w\\x\\y\\z\";\n}\n", + "a {\n color: \"\\b\\c\\d\\e\\fghijklmnopqrstuvwxyz\";\n}\n" +); +grass_test!( + interpolated_inside_string_does_not_produce_unquoted_output, + "a {\n color: \"#{\"\\b\"}\";\n}\n", + "a {\n color: \"\\b\";\n}\n" +); +grass_test!( + unquote_quoted_backslash_single_lowercase_hex_char, + "a {\n color: #{\"\\b\"};\n}\n", + "a {\n color: \x0b;\n}\n" +); +grass_test!( + unquoted_escape_equality, + "a {\n color: foo == f\\6F\\6F;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + quoted_escape_zero, + "a {\n color: \"\\0\";\n}\n", + "@charset \"UTF-8\";\na {\n color: \"�\";\n}\n" +); +grass_test!( + unquoted_escape_zero, + "a {\n color: \\0;\n}\n", + "a {\n color: \\0 ;\n}\n" +); +grass_test!( + quote_escape, + "a {\n color: quote(\\b);\n}\n", + "a {\n color: \"\\\\b \";\n}\n" +); +grass_test!(escaped_backslash, "a {\n color: \"\\\\\";\n}\n"); +grass_test!( + double_quotes_when_containing_single_quote, + "a {\n color: '\\\'';\n}\n", + "a {\n color: \"'\";\n}\n" +); +grass_test!( + allows_escaped_quote_at_start_of_ident, + "a {\n color: \\\"c\\\";\n}\n" +); +grass_test!( + quoted_escaped_newline_unchanged, + "a {\n color: \"\\a\";\n}\n" +); +grass_test!( + unquoted_escape_minus_unquoted, + "a {\n color: \\a - foo;\n}\n" +); +grass_test!( + quoted_escaped_tab, + "a {\n color: \"\\9\";\n}\n", + "a {\n color: \"\t\";\n}\n" +); +grass_test!( + unquoted_escaped_tab, + "a {\n color: \\9;\n}\n", + "a {\n color: \\9 ;\n}\n" +); +grass_error!( + escape_sequence_does_not_fit_inside_char, + "a {\n color: \\110000;\n}\n", + "Error: Invalid escape sequence." +); +grass_test!( + escaped_newline_in_quoted_string, + "a {\n color: \"foo\\\nbar\";\n}\n", + "a {\n color: \"foobar\";\n}\n" +); diff --git a/css/parser/tests/grass_strings.rs b/css/parser/tests/grass_strings.rs new file mode 100644 index 000000000000..a2224e0e6bc6 --- /dev/null +++ b/css/parser/tests/grass_strings.rs @@ -0,0 +1,244 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + uppercase_ident, + "a {\n color: to-upper-case(aBc123);\n}\n", + "a {\n color: ABC123;\n}\n" +); +grass_test!( + lowercase_ident, + "a {\n color: to-lower-case(AbC123);\n}\n", + "a {\n color: abc123;\n}\n" +); +grass_error!( + uppercase_non_ident, + "a {\n color: to-upper-case(123);\n}\n", + "Error: $string: 123 is not a string." +); +grass_error!( + lowercase_non_ident, + "a {\n color: to-lower-case(123);\n}\n", + "Error: $string: 123 is not a string." +); +grass_test!( + uppercase_named_arg, + "a {\n color: to-upper-case($string: aBc123);\n}\n", + "a {\n color: ABC123;\n}\n" +); +grass_test!( + lowercase_named_arg, + "a {\n color: to-lower-case($string: AbC123);\n}\n", + "a {\n color: abc123;\n}\n" +); +grass_test!( + length_ident, + "a {\n color: str-length(AbC123);\n}\n", + "a {\n color: 6;\n}\n" +); +grass_test!( + length_named_arg, + "a {\n color: str-length($string: aBc123);\n}\n", + "a {\n color: 6;\n}\n" +); +grass_test!( + str_slice_dbl_quote, + "a {\n color: str-slice(\"abcd\", 2, 3);\n}\n", + "a {\n color: \"bc\";\n}\n" +); +grass_test!( + str_slice_sgl_quote, + "a {\n color: str-slice('abcd', 2, 3);\n}\n", + "a {\n color: \"bc\";\n}\n" +); +grass_test!( + str_slice_no_quote, + "a {\n color: str-slice(abcd, 2, 3);\n}\n", + "a {\n color: bc;\n}\n" +); +grass_test!( + str_slice_no_end, + "a {\n color: str-slice(abcd, 2);\n}\n", + "a {\n color: bcd;\n}\n" +); +grass_test!( + str_slice_negative_start_negative_end, + "a {\n color: str-slice(abcd, -3, -2);\n}\n", + "a {\n color: bc;\n}\n" +); +grass_test!( + str_slice_negative_end, + "a {\n color: str-slice(abcd, 2, -2);\n}\n", + "a {\n color: bc;\n}\n" +); +grass_test!( + str_slice_start_0, + "a {\n color: str-slice(cde, 0);\n}\n", + "a {\n color: cde;\n}\n" +); +grass_test!( + str_slice_start_below_negative_str_len, + "a {\n color: str-slice(cde, -100);\n}\n", + "a {\n color: cde;\n}\n" +); +grass_test!( + str_slice_end_below_negative_str_len, + "a {\n color: str-slice(\"cde\", 0, -100);\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + str_slice_end_0, + "a {\n color: str-slice(\"cde\", 1, 0);\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + str_slice_bigger_than_usize_max, + "a {\n color: str-slice($string: \"foo\", $start-at: -99999999999999999999, $end-at: \ + 99999999999999999999);\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!( + str_slice_positive_index_bigger_than_usize_max, + "a {\n color: str-slice($string: \"foo\", $start-at: 99999999999999999999, $end-at: \ + -99999999999999999999);\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + str_slice_start_end_equal, + "a {\n color: str-slice(\"cde\", 1, 1);\n}\n", + "a {\n color: \"c\";\n}\n" +); +grass_test!( + str_len_dbl_quotes, + "a {\n color: str-length(\"cde\");\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + str_len_unquoted, + "a {\n color: str-length(cde);\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + unquote_empty_string_is_null, + "a {\n color: unquote('');\n}\n", + "" +); +grass_test!( + str_len_space, + "a {\n color: str-length(\"foo bar\");\n}\n", + "a {\n color: 7;\n}\n" +); +grass_test!( + str_len_double_wide, + "a {\n color: str-length(\"👭\");\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + str_len_combining, + "a {\n color: str-length(\"c\\0308\");\n}\n", + "a {\n color: 2;\n}\n" +); +grass_test!( + str_index_char, + "a {\n color: str-index(abcd, a);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + str_index_str, + "a {\n color: str-index(abcd, ab);\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!(str_index_null, "a {\n color: str-index(abcd, X);\n}\n", ""); +grass_test!( + str_insert_start, + "a {\n color: str-insert(\"abcd\", \"X\", 1);\n}\n", + "a {\n color: \"Xabcd\";\n}\n" +); +grass_test!( + str_insert_middle, + "a {\n color: str-insert(\"abcd\", \"X\", 4);\n}\n", + "a {\n color: \"abcXd\";\n}\n" +); +grass_test!( + str_insert_end, + "a {\n color: str-insert(\"abcd\", \"X\", 5);\n}\n", + "a {\n color: \"abcdX\";\n}\n" +); +grass_test!( + str_insert_sgl_quotes, + "a {\n color: str-insert('abcd', \"X\", 4);\n}\n", + "a {\n color: \"abcXd\";\n}\n" +); +grass_test!( + str_insert_no_quotes, + "a {\n color: str-insert(abcd, \"X\", 4);\n}\n", + "a {\n color: abcXd;\n}\n" +); +grass_test!( + str_insert_empty_string, + "a {\n color: str-insert(\"\", \"abcd\", 4);\n}\n", + "a {\n color: \"abcd\";\n}\n" +); +grass_test!( + str_insert_empty_substring, + "a {\n color: str-insert(abcd, \"\", 4);\n}\n", + "a {\n color: abcd;\n}\n" +); +grass_test!( + str_insert_idx_0, + "a {\n color: str-insert(abcd, \"X\", 0);\n}\n", + "a {\n color: Xabcd;\n}\n" +); +grass_test!( + str_insert_negative_1, + "a {\n color: str-insert(abc, \"X\", -1);\n}\n", + "a {\n color: abcX;\n}\n" +); +grass_test!( + str_insert_negative_2, + "a {\n color: str-insert(abc, \"X\", -2);\n}\n", + "a {\n color: abXc;\n}\n" +); +grass_test!( + str_insert_negative_3, + "a {\n color: str-insert(abc, \"X\", -3);\n}\n", + "a {\n color: aXbc;\n}\n" +); +grass_error!( + str_insert_float_idx, + "a {\n color: str-insert(abcd, \"X\", .5);\n}\n", + "Error: $index: 0.5 is not an int." +); +grass_error!( + str_insert_idx_with_units, + "a {\n color: str-insert(abcd, \"X\", 5px);\n}\n", + "Error: $index: Expected 5px to have no units." +); +grass_test!( + str_insert_idx_larger_than_string, + "a {\n color: str-insert(abcd, \"X\", 20);\n}\n", + "a {\n color: abcdX;\n}\n" +); +grass_test!( + str_insert_idx_larger_than_string_negative, + "a {\n color: str-insert(abcd, \"X\", -20);\n}\n", + "a {\n color: Xabcd;\n}\n" +); +grass_test!( + str_insert_double_width_char, + "a {\n color: str-insert(\"👭\", \"c\", 2);\n}\n", + "@charset \"UTF-8\";\na {\n color: \"👭c\";\n}\n" +); +grass_test!( + str_insert_positive_index_bigger_than_usize_max, + "a {\n color: str-insert($string: \"foo\", $insert: \"X\", $index: \ + 99999999999999999999);\n}\n", + "a {\n color: \"fooX\";\n}\n" +); +grass_test!( + str_insert_negative_index_bigger_than_usize_max, + "a {\n color: str-insert($string: \"foo\", $insert: \"X\", $index: \ + -99999999999999999999);\n}\n", + "a {\n color: \"Xfoo\";\n}\n" +); +grass_test!(hash_in_string, "a {\n color: \"#foo\";\n}\n"); diff --git a/css/parser/tests/grass_styles.rs b/css/parser/tests/grass_styles.rs new file mode 100644 index 000000000000..99087c5ce1e1 --- /dev/null +++ b/css/parser/tests/grass_styles.rs @@ -0,0 +1,177 @@ +#[macro_use] +mod grass_macros; + +grass_test!(basic_style, "a {\n color: red;\n}\n"); +grass_test!(two_styles, "a {\n color: red;\n color: blue;\n}\n"); +grass_test!( + two_inner_rulesets, + "a {\n b {\n color: red;\n}\n c {\n color: white;\n}\n}\n", + "a b {\n color: red;\n}\na c {\n color: white;\n}\n" +); +grass_test!( + two_rulesets, + "a {\n color: red;\n}\n\nc {\n color: white;\n}\n" +); +grass_test!( + two_rulesets_first_no_semicolon, + "a {\n color: red\n}\nc {\n color: white;\n}\n", + "a {\n color: red;\n}\n\nc {\n color: white;\n}\n" +); +grass_test!( + two_inner_outer_rulesets, + "a {\n b {\n color: red;\n}\n c {\n color: white;\n}\n}\na {\n b {\n color: red;\n}\n \ + c {\n color: white;\n}\n}\n", + "a b {\n color: red;\n}\na c {\n color: white;\n}\n\na b {\n color: red;\n}\na c {\n \ + color: white;\n}\n" +); +grass_test!( + removes_empty_outer_styles, + "a {\n b {\n color: red;\n }\n", + "a b {\n color: red;\n}\n" +); +grass_test!(removes_empty_styles, "a {}\n", ""); +grass_test!( + doesnt_eat_style_after_ruleset, + "a {\n b {\n color: red;\n}\n color: blue;\n}\n", + "a {\n color: blue;\n}\na b {\n color: red;\n}\n" +); +grass_test!( + multiline_style, + "a {\n color: red\n blue;\n}\n", + "a {\n color: red blue;\n}\n" +); +grass_test!(hyphenated_style_property, "a {\n font-family: Arial;\n}\n"); +grass_test!(hyphenated_style_value, "a {\n color: Open-Sans;\n}\n"); +grass_test!( + space_separated_style_value, + "a {\n border: solid red;\n}\n" +); +grass_test!( + single_quoted_style_value, + "a {\n font: 'Open-Sans';\n}\n", + "a {\n font: \"Open-Sans\";\n}\n" +); +grass_test!( + double_quoted_style_value, + "a {\n font: \"Open-Sans\";\n}\n" +); +grass_test!( + comma_style_value, + "a {\n font: Open-Sans, sans-serif;\n}\n" +); +grass_test!( + style_interpolation_start, + "a {\n #{c}olor: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_interpolation_middle, + "a {\n co#{l}or: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_interpolation_end, + "a {\n colo#{r}: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_interpolation_variable, + "$a: foo;\na {\n co#{$a}lor: red;\n}\n", + "a {\n cofoolor: red;\n}\n" +); +grass_test!( + style_val_interpolation_start, + "a {\n color: #{r}ed;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_val_interpolation_middle, + "a {\n color: r#{e}d;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_val_interpolation_end, + "a {\n color: re#{d};\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + style_val_interpolation_variable, + "$a: foo;\na {\n color: r#{$a}ed;\n}\n", + "a {\n color: rfooed;\n}\n" +); +grass_test!( + style_whitespace, + "a {\n color : red ; \n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + triple_nested_preceding_ruleset, + "a {\n b {\n foo: bar;\n c {}\n }\n}\n", + "a b {\n foo: bar;\n}\n" +); +grass_test!( + triple_nested_following_ruleset, + "a {\n b {\n c {}\n foo: bar;\n }\n}\n", + "a b {\n foo: bar;\n}\n" +); +grass_test!( + single_nested_styles, + "a {\n webkit: {\n color: red;\n color: orange\n }\n}\n", + "a {\n webkit-color: red;\n webkit-color: orange;\n}\n" +); +grass_test!( + multiple_nested_styles, + "a {\n webkit: {\n webkit: {\n color: red;\n }\n }\n}\n", + "a {\n webkit-webkit-color: red;\n}\n" +); +grass_test!( + no_space_after_colon_before_nested_style, + "a {\n foo:{\n bar: baz\n }\n}\n", + "a {\n foo-bar: baz;\n}\n" +); +grass_test!( + no_space_between_colon, + "a {\n color:red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + no_space_between_colon_no_semicolon, + "a {\n color:red\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!(removes_null_value, "a {\n color: null;\n}\n", ""); +grass_test!( + namespace_before_open_brace, + "foo {\n a: b {\n c: d;\n }\n}\n", + "foo {\n a: b;\n a-c: d;\n}\n" +); +grass_test!( + namespace_before_open_brace_nested, + "foo {\n a: b {\n c: d {\n e: f;\n }\n }\n}\n", + "foo {\n a: b;\n a-c: d;\n a-c-e: f;\n}\n" +); +grass_test!(curly_braces_in_quotes, "a {\n color: \"{foo}\";\n}\n"); +grass_test!( + escaped_interpolation, + "a {\n color: \"\\#{foo}\";\n}\n", + "a {\n color: \"#{foo}\";\n}\n" +); +grass_test!( + styles_after_quoted, + "a {\n color: \"red\";\n color: blue;\n}\n" +); +grass_test!( + emits_leading_whitespace, + "a {\n color: unquote(\" foo\");\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + emits_trailing_whitespace, + "a {\n color: unquote(\"foo \");\n}\n", + "a {\n color: foo ;\n}\n" +); +grass_test!( + multiline_comment_after_style_property, + "a {\n color /**/ : red;\n}\n", + "a {\n color: red;\n}\n" +); diff --git a/css/parser/tests/grass_subtraction.rs b/css/parser/tests/grass_subtraction.rs new file mode 100644 index 000000000000..65eac990a971 --- /dev/null +++ b/css/parser/tests/grass_subtraction.rs @@ -0,0 +1,329 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + subs_idents, + "a {\n color: foo - bar;\n}\n", + "a {\n color: foo-bar;\n}\n" +); +grass_test!( + subs_dbl_quoted_idents, + "a {\n color: \"foo\" - \"bar\";\n}\n", + "a {\n color: \"foo\"-\"bar\";\n}\n" +); +grass_test!( + subs_sgl_quoted_idents, + "a {\n color: 'foo' - 'bar';\n}\n", + "a {\n color: \"foo\"-\"bar\";\n}\n" +); +grass_test!( + subs_dbl_and_un_quoted_idents, + "a {\n color: \"foo\" - bar;\n}\n", + "a {\n color: \"foo\"-bar;\n}\n" +); +grass_test!( + subs_sgl_and_un_quoted_idents, + "a {\n color: 'foo' - bar;\n}\n", + "a {\n color: \"foo\"-bar;\n}\n" +); +grass_test!( + subs_un_and_dbl_quoted_idents, + "a {\n color: foo - \"bar\";\n}\n", + "a {\n color: foo-\"bar\";\n}\n" +); +grass_test!( + subs_un_and_sgl_quoted_idents, + "a {\n color: foo - 'bar';\n}\n", + "a {\n color: foo-\"bar\";\n}\n" +); +grass_test!( + subs_sgl_and_dbl_quoted_idents, + "a {\n color: 'foo' - \"bar\";\n}\n", + "a {\n color: \"foo\"-\"bar\";\n}\n" +); +grass_test!( + subs_dbl_and_sgl_quoted_idents, + "a {\n color: \"foo\" - 'bar';\n}\n", + "a {\n color: \"foo\"-\"bar\";\n}\n" +); +grass_test!( + subs_ident_true, + "a {\n color: foo - true;\n}\n", + "a {\n color: foo-true;\n}\n" +); +grass_test!( + subs_dbl_quoted_ident_true, + "a {\n color: \"foo\" - true;\n}\n", + "a {\n color: \"foo\"-true;\n}\n" +); +grass_test!( + subs_ident_false, + "a {\n color: foo - false;\n}\n", + "a {\n color: foo-false;\n}\n" +); +grass_test!( + subs_dbl_quoted_ident_false, + "a {\n color: \"foo\" - false;\n}\n", + "a {\n color: \"foo\"-false;\n}\n" +); +grass_test!( + subs_ident_important, + "a {\n color: foo - !important;\n}\n", + "a {\n color: foo-!important;\n}\n" +); +grass_test!( + subs_ident_null, + "a {\n color: foo - null;\n}\n", + "a {\n color: foo-;\n}\n" +); +grass_test!( + subs_dbl_quoted_ident_null, + "a {\n color: \"foo\" - null;\n}\n", + "a {\n color: \"foo\"-;\n}\n" +); +grass_test!( + subs_sgl_quoted_ident_null, + "a {\n color: 'foo' - null;\n}\n", + "a {\n color: \"foo\"-;\n}\n" +); +grass_test!( + subs_ident_number, + "a {\n color: foo - 1;\n}\n", + "a {\n color: foo-1;\n}\n" +); +grass_test!( + subs_dbl_quoted_ident_number, + "a {\n color: \"foo\" - 1;\n}\n", + "a {\n color: \"foo\"-1;\n}\n" +); +grass_test!( + subs_sgl_quoted_ident_number, + "a {\n color: 'foo' - 1;\n}\n", + "a {\n color: \"foo\"-1;\n}\n" +); +grass_test!( + subs_ident_dimension, + "a {\n color: foo - 1px;\n}\n", + "a {\n color: foo-1px;\n}\n" +); +grass_test!( + num_minus_list, + "a {\n color: 1 - (2 3);\n}\n", + "a {\n color: 1-2 3;\n}\n" +); +grass_test!( + list_minus_num, + "a {\n color: (1 2) - 3;\n}\n", + "a {\n color: 1 2-3;\n}\n" +); +grass_test!( + dblquoted_minus_list, + "a {\n color: \"1\" - (2 3);\n}\n", + "a {\n color: \"1\"-2 3;\n}\n" +); +grass_test!( + list_minus_dblquoted, + "a {\n color: (1 2) - \"3\";\n}\n", + "a {\n color: 1 2-\"3\";\n}\n" +); +grass_test!( + sglquoted_minus_list, + "a {\n color: 'a' - (b c);\n}\n", + "a {\n color: \"a\"-b c;\n}\n" +); +grass_test!( + list_minus_sglquoted, + "a {\n color: (b c) - 'a';\n}\n", + "a {\n color: b c-\"a\";\n}\n" +); +grass_test!( + list_minus_list, + "a {\n color: (a b) - (1 2);\n}\n", + "a {\n color: a b-1 2;\n}\n" +); +grass_test!( + subs_dbl_quoted_ident_dimension, + "a {\n color: \"foo\" - 1px;\n}\n", + "a {\n color: \"foo\"-1px;\n}\n" +); +grass_test!( + subs_sgl_quoted_ident_dimension, + "a {\n color: 'foo' - 1px;\n}\n", + "a {\n color: \"foo\"-1px;\n}\n" +); +grass_test!( + number_minus_unquoted_ident, + "a {\n color: 1 - foo;\n}\n", + "a {\n color: 1-foo;\n}\n" +); +grass_test!( + number_minus_sglquoted_ident, + "a {\n color: 1 - 'foo';\n}\n", + "a {\n color: 1-\"foo\";\n}\n" +); +grass_test!( + number_minus_dblquoted_ident, + "a {\n color: 1 - \"foo\";\n}\n", + "a {\n color: 1-\"foo\";\n}\n" +); +grass_test!( + number_minus_minus_number, + "a {\n color: 1 - - 2;\n}\n", + "a {\n color: 3;\n}\n" +); +grass_test!( + sub_no_space, + "a {\n color: 10-10;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + sub_space_on_left, + "a {\n color: 10 -10;\n}\n", + "a {\n color: 10 -10;\n}\n" +); +grass_test!( + sub_space_on_right, + "a {\n color: 10- 10;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + sub_space_on_both, + "a {\n color: 10 - 10;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + sub_no_space_interpolation, + "a {\n color: 10-#{10};\n}\n", + "a {\n color: 10 -10;\n}\n" +); +grass_test!( + plus_after_minus, + "a {\n plus-after-minus: 1 - + 2;\n}\n", + "a {\n plus-after-minus: -1;\n}\n" +); +grass_test!( + multiline_comments_between_operands, + "a {\n color: 1/**/-/**/1;\n}\n", + "a {\n color: 0;\n}\n" +); +grass_test!( + no_space_after_first_unit_and_second_float, + "a {\n color: 1em- 0.0em;\n}\n", + "a {\n color: 1em- 0em;\n}\n" +); +grass_test!( + null_minus_number, + "a {\n color: null - 1;\n}\n", + "a {\n color: -1;\n}\n" +); +grass_test!( + null_minus_unquoted_string, + "a {\n color: null - foo;\n}\n", + "a {\n color: -foo;\n}\n" +); +grass_test!( + number_minus_true, + "a {\n color: 1 - true;\n}\n", + "a {\n color: 1-true;\n}\n" +); +grass_test!( + number_minus_false, + "a {\n color: 1 - false;\n}\n", + "a {\n color: 1-false;\n}\n" +); +grass_test!( + number_minus_important, + "a {\n color: 1 - !important;\n}\n", + "a {\n color: 1-!important;\n}\n" +); +grass_test!( + number_minus_null, + "a {\n color: 1 - null;\n}\n", + "a {\n color: 1-;\n}\n" +); +grass_test!( + number_minus_arglist, + "@function foo($a...) { + @return 1 - $a; + } + + a { + color: foo(a, b); + }", + "a {\n color: 1-a, b;\n}\n" +); +grass_test!( + color_minus_unquoted, + "a {\n color: red - foo;\n}\n", + "a {\n color: red-foo;\n}\n" +); +grass_test!( + color_minus_dblquoted, + "a {\n color: red - \"foo\";\n}\n", + "a {\n color: red-\"foo\";\n}\n" +); +grass_test!( + color_minus_sglquoted, + "a {\n color: red - 'foo';\n}\n", + "a {\n color: red-\"foo\";\n}\n" +); +grass_test!( + color_minus_important, + "a {\n color: red - !important;\n}\n", + "a {\n color: red-!important;\n}\n" +); +grass_test!( + color_minus_null, + "a {\n color: red - null;\n}\n", + "a {\n color: red-;\n}\n" +); +grass_test!( + ident_minus_color, + "a {\n color: foo - red;\n}\n", + "a {\n color: foo-red;\n}\n" +); +grass_test!( + sub_nan_left, + "a {\n left:0/0-0;\n}\n", + "a {\n left: NaN;\n}\n" +); +grass_test!( + sub_nan_right, + "a {\n left:0-0/0;\n}\n", + "a {\n left: NaN;\n}\n" +); +grass_error!( + number_minus_color, + "a {\n color: 1 - #abc;\n}\n", + "Error: Undefined operation \"1 - #abc\"." +); +grass_error!( + number_minus_hex_color_no_space, + "a {\n color: 1-#abc;\n}\n", + "Error: Undefined operation \"1 - #abc\"." +); +grass_error!( + null_minus_function, + "a {\n color: null - get-function(lighten);\n}\n", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_error!( + map_lhs_sub, + "a {color: (a: b) - 1;}", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + map_rhs_sub, + "a {color: 1 - (a: b);}", + "Error: (a: b) isn't a valid CSS value." +); +grass_error!( + function_lhs_sub, + "a {color: get-function(lighten) - 1;}", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); +grass_error!( + function_rhs_sub, + "a {color: 1 - get-function(lighten);}", + "Error: get-function(\"lighten\") isn't a valid CSS value." +); diff --git a/css/parser/tests/grass_supports.rs b/css/parser/tests/grass_supports.rs new file mode 100644 index 000000000000..56b6bcd5cd25 --- /dev/null +++ b/css/parser/tests/grass_supports.rs @@ -0,0 +1,16 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + style_following, + "@supports (a: b) { + a { + color: red; + } + } + + a { + color: green; + }", + "@supports (a: b) {\n a {\n color: red;\n }\n}\na {\n color: green;\n}\n" +); diff --git a/css/parser/tests/grass_unary.rs b/css/parser/tests/grass_unary.rs new file mode 100644 index 000000000000..6b6a65600289 --- /dev/null +++ b/css/parser/tests/grass_unary.rs @@ -0,0 +1,61 @@ +#[macro_use] +mod grass_macros; + +grass_test!(unary_pos_unquoted_ident, "a {\n color: +foo;\n}\n"); +grass_test!( + unary_pos_whitespace, + "a {\n color: + foo;\n}\n", + "a {\n color: +foo;\n}\n" +); +grass_test!(unary_pos_dblquoted_ident, "a {\n color: +\"foo\";\n}\n"); +grass_test!( + unary_pos_sglquoted_ident, + "a {\n color: +'foo';\n}\n", + "a {\n color: +\"foo\";\n}\n" +); +grass_test!(unary_pos_color, "a {\n color: +\"foo\";\n}\n"); +grass_test!( + unary_pos_number_unit, + "a {\n color: +1px;\n}\n", + "a {\n color: 1px;\n}\n" +); +grass_test!( + unary_pos_number, + "a {\n color: +10;\n}\n", + "a {\n color: 10;\n}\n" +); +grass_test!( + unary_pos_in_list, + "a {\n color: bar,+ \"bar\" - foo;\n}\n", + "a {\n color: bar, +\"bar\"-foo;\n}\n" +); +grass_test!(unary_neg_unquoted_ident, "a {\n color: -foo;\n}\n"); +grass_test!(unary_neg_dblquoted_ident, "a {\n color: -\"foo\";\n}\n"); +grass_test!( + unary_neg_sglquoted_ident, + "a {\n color: -'foo';\n}\n", + "a {\n color: -\"foo\";\n}\n" +); +grass_test!(unary_neg_color, "a {\n color: -\"foo\";\n}\n"); +grass_test!(unary_neg_number, "a {\n color: -1px;\n}\n"); +grass_test!( + unary_neg_whitespace, + "a {\n color: - 1px;\n}\n", + "a {\n color: -1px;\n}\n" +); +grass_test!( + unary_neg_number_type, + "a {\n color: type-of(- 1px);\n}\n", + "a {\n color: number;\n}\n" +); +grass_test!( + unary_neg_variable, + "$a: 1;\n\na {\n color: -$a;\n}\n", + "a {\n color: -1;\n}\n" +); +grass_test!( + unary_neg_null_paren, + "a {\n color: -(null);\n}\n", + "a {\n color: -;\n}\n" +); +grass_test!(negative_null_as_ident, "a {\n color: -null;\n}\n"); diff --git a/css/parser/tests/grass_unicode_range.rs b/css/parser/tests/grass_unicode_range.rs new file mode 100644 index 000000000000..6596b844d475 --- /dev/null +++ b/css/parser/tests/grass_unicode_range.rs @@ -0,0 +1,45 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + single_codepoint, + "a {\n color: U+26;\n}\n", + "a {\n color: U+26;\n}\n" +); +grass_test!( + simple_range, + "a {\n color: U+0-7F;\n}\n", + "a {\n color: U+0-7F;\n}\n" +); +grass_test!( + simple_wildcard_range, + "a {\n color: U+45????;\n}\n", + "a {\n color: U+45????;\n}\n" +); +grass_test!( + lowercase_u, + "a {\n color: u+27a;\n}\n", + "a {\n color: u+27a;\n}\n" +); +grass_error!( + interpolated_range, + "a {\n color: U+2A#{70}C;\n}\n", + "Error: Expected end of identifier." +); +grass_error!( + unicode_escape_within_range, + "a {\n color: U+B\\a;\n}\n", + "Error: Expected end of identifier." +); +grass_error!( + longer_than_6_characters, + "a {\n color: U+1234567;\n}\n", + "Error: Expected end of identifier." +); +// I believe this to be a bug in the dart-sass implementation +// we test for it to ensure full parity +grass_test!( + length_of_6_with_question_mark, + "a {\n color: U+123456?;\n}\n", + "a {\n color: U+123456?;\n}\n" +); diff --git a/css/parser/tests/grass_units.rs b/css/parser/tests/grass_units.rs new file mode 100644 index 000000000000..7286cd1bea9b --- /dev/null +++ b/css/parser/tests/grass_units.rs @@ -0,0 +1,292 @@ +#[macro_use] +mod grass_macros; + +grass_test!(unit_none, "a {\n height: 1;\n}\n"); +grass_test!(unit_not_attached, "a {\n height: 1 px;\n}\n"); +grass_test!(unit_px, "a {\n height: 1px;\n}\n"); +grass_test!(unit_em, "a {\n height: 1em;\n}\n"); +grass_test!(unit_rem, "a {\n height: 1rem;\n}\n"); +grass_test!(unit_percent, "a {\n height: 1%;\n}\n"); +grass_test!( + unit_times_none, + "a {\n color: 3px * 2;\n}\n", + "a {\n color: 6px;\n}\n" +); +grass_test!( + none_times_unit, + "a {\n color: 2 * 3px;\n}\n", + "a {\n color: 6px;\n}\n" +); +grass_test!( + unit_fn_unit_times_none, + "a {\n color: unit(1px * 1);\n}\n", + "a {\n color: \"px\";\n}\n" +); +grass_test!( + unit_fn_none_times_unit, + "a {\n color: unit(1 * 1px);\n}\n", + "a {\n color: \"px\";\n}\n" +); +grass_test!( + unit_fn_unit_times_unit, + "a {\n color: unit(1px*1px);\n}\n", + "a {\n color: \"px*px\";\n}\n" +); +grass_test!( + unit_fn_unit_times_unit_times_unit, + "a {\n color: unit(1px * 1rad * 1em);\n}\n", + "a {\n color: \"px*rad*em\";\n}\n" +); +grass_test!( + unit_none_times_none_times_none, + "a {\n color: 1 * 1 * 1;\n}\n", + "a {\n color: 1;\n}\n" +); +grass_test!( + unit_plus_none, + "a {\n color: 10px + 10;\n}\n", + "a {\n color: 20px;\n}\n" +); +grass_test!( + none_plus_unit, + "a {\n color: 10 + 10px;\n}\n", + "a {\n color: 20px;\n}\n" +); +grass_test!( + unit_minus_none, + "a {\n color: 10px - 10;\n}\n", + "a {\n color: 0px;\n}\n" +); +grass_test!( + none_minus_unit, + "a {\n color: 10 - 10px;\n}\n", + "a {\n color: 0px;\n}\n" +); +grass_test!( + percent_plus_none, + "a {\n color: 10% + 10;\n}\n", + "a {\n color: 20%;\n}\n" +); +grass_test!( + unit_no_hyphen, + "a {\n color: 1px-2px;\n}\n", + "a {\n color: -1px;\n}\n" +); +grass_test!( + unit_starts_with_escape_sequence, + "a {\n color: 1\\9;\n}\n", + "a {\n color: 1\\9 ;\n}\n" +); +grass_test!( + unit_fn_starts_with_escape_sequence, + "a {\n color: unit(1\\9);\n}\n", + "a {\n color: \"\\\\9 \";\n}\n" +); +grass_test!( + non_ascii_numeric_interpreted_as_unit, + "a {\n color: 2߄;\n}\n", + "@charset \"UTF-8\";\na {\n color: 2߄;\n}\n" +); +grass_test!( + unit_div_same, + "a {\n color: unit(1em / 1em);\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + unit_div_first_none, + "a {\n color: unit(1 / 1em);\n}\n", + "a {\n color: \"em^-1\";\n}\n" +); +grass_test!( + unit_div_second_none, + "a {\n color: unit(1em / 1);\n}\n", + "a {\n color: \"em\";\n}\n" +); +grass_test!( + unit_div_comparable, + "a {\n color: unit(1in / 1px);\n color: (1in / 1px);\n}\n", + "a {\n color: \"\";\n color: 96;\n}\n" +); +grass_test!( + unit_mul_times_mul, + "a {\n color: unit((1em * 1px) * (1em * 1px));\n}\n", + "a {\n color: \"em*px*em*px\";\n}\n" +); +grass_test!( + unit_single_times_mul, + "a {\n color: unit(1in * (1em * 1px));\n}\n", + "a {\n color: \"in*em*px\";\n}\n" +); +grass_test!( + unit_div_lhs_mul_uncomparable, + "a {\n color: unit((1 / 1in) * 1em);\n}\n", + "a {\n color: \"em/in\";\n}\n" +); +grass_test!( + unit_div_lhs_mul_same, + "a {\n color: unit((1 / 1in) * 1in);\n}\n", + "a {\n color: \"\";\n}\n" +); +grass_test!( + unit_begins_with_single_hyphen, + "a {\n color: unit(1-em);\n}\n", + "a {\n color: \"-em\";\n}\n" +); +grass_test!( + unit_begins_with_two_hyphens, + "a {\n color: 1--em;\n}\n", + "a {\n color: 1 --em;\n}\n" +); +grass_test!( + unit_begins_with_escape_sequence, + "a {\n color: unit(1\\65);\n}\n", + "a {\n color: \"e\";\n}\n" +); +grass_test!( + unit_begins_with_escape_sequence_followed_by_space_and_hyphen, + "a {\n color: unit(1\\65 -);\n}\n", + "a {\n color: \"e-\";\n}\n" +); +grass_test!( + unit_begins_with_single_hyphen_followed_by_escape_sequence, + "a {\n color: unit(1-\\65);\n}\n", + "a {\n color: \"-e\";\n}\n" +); +grass_error!( + display_single_div_with_none_numerator, + "a {\n color: (1 / 1em);\n}\n", + "Error: 1em^-1 isn't a valid CSS value." +); +grass_error!( + #[ignore = "non-comparable inverse units"] + display_single_div_with_non_comparable_numerator, + "a {\n color: (1px / 1em);\n}\n", + "Error: 1px/em isn't a valid CSS value." +); +grass_error!( + display_single_mul, + "a {\n color: 1rem * 1px;\n}\n", + "Error: 1rem*px isn't a valid CSS value." +); +grass_error!( + display_arbitrary_mul, + "a {\n color: 1rem * 1px * 1rad * 1foo;\n}\n", + "Error: 1rem*px*rad*foo isn't a valid CSS value." +); +grass_error!( + display_single_div_with_none_numerator_percent, + "a {\n color: (35 / 7%);\n}\n", + "Error: 5%^-1 isn't a valid CSS value." +); + +macro_rules! test_unit_addition { + ($u1:ident, $u2:ident, $out:literal) => { + paste::item!( + grass_test!( + [<$u1 _plus_ $u2>], + concat!("a {\n color: 1", stringify!($u1), " + 1", stringify!($u2), ";\n}\n"), + &format!("a {{\n color: {}{};\n}}\n", $out, stringify!($u1)) + ); + ); + }; +} + +test_unit_addition!(in, in, "2"); +test_unit_addition!(in, cm, "1.3937007874"); +test_unit_addition!(in, pc, "1.1666666667"); +test_unit_addition!(in, mm, "1.0393700787"); +test_unit_addition!(in, q, "1.0098425197"); +test_unit_addition!(in, pt, "1.0138888889"); +test_unit_addition!(in, px, "1.0104166667"); + +test_unit_addition!(cm, in, "3.54"); +test_unit_addition!(cm, cm, "2"); +test_unit_addition!(cm, pc, "1.4233333333"); +test_unit_addition!(cm, mm, "1.1"); +test_unit_addition!(cm, q, "1.025"); +test_unit_addition!(cm, pt, "1.0352777778"); +test_unit_addition!(cm, px, "1.0264583333"); + +test_unit_addition!(pc, in, "7"); +test_unit_addition!(pc, cm, "3.3622047244"); +test_unit_addition!(pc, pc, "2"); +test_unit_addition!(pc, mm, "1.2362204724"); +test_unit_addition!(pc, q, "1.0590551181"); +test_unit_addition!(pc, pt, "1.0833333333"); +test_unit_addition!(pc, px, "1.0625"); + +test_unit_addition!(mm, in, "26.4"); +test_unit_addition!(mm, cm, "11"); +test_unit_addition!(mm, pc, "5.2333333333"); +test_unit_addition!(mm, mm, "2"); +test_unit_addition!(mm, q, "1.25"); +test_unit_addition!(mm, pt, "1.3527777778"); +test_unit_addition!(mm, px, "1.2645833333"); + +test_unit_addition!(q, in, "102.6"); +test_unit_addition!(q, cm, "41"); +test_unit_addition!(q, pc, "17.9333333333"); +test_unit_addition!(q, mm, "5"); +test_unit_addition!(q, q, "2"); +test_unit_addition!(q, pt, "2.4111111111"); +test_unit_addition!(q, px, "2.0583333333"); + +test_unit_addition!(pt, in, "73"); +test_unit_addition!(pt, cm, "29.3464566929"); +test_unit_addition!(pt, pc, "13"); +test_unit_addition!(pt, mm, "3.8346456693"); +test_unit_addition!(pt, q, "1.7086614173"); +test_unit_addition!(pt, pt, "2"); +test_unit_addition!(pt, px, "1.75"); + +test_unit_addition!(px, in, "97"); +test_unit_addition!(px, cm, "38.7952755906"); +test_unit_addition!(px, pc, "17"); +test_unit_addition!(px, mm, "4.7795275591"); +test_unit_addition!(px, q, "1.9448818898"); +test_unit_addition!(px, pt, "2.3333333333"); +test_unit_addition!(px, px, "2"); + +test_unit_addition!(deg, deg, "2"); +test_unit_addition!(deg, grad, "1.9"); +test_unit_addition!(deg, rad, "58.2957795131"); +test_unit_addition!(deg, turn, "361"); + +test_unit_addition!(grad, deg, "2.1111111111"); +test_unit_addition!(grad, grad, "2"); +test_unit_addition!(grad, rad, "64.6619772368"); +test_unit_addition!(grad, turn, "401"); + +test_unit_addition!(rad, deg, "1.0174532925"); +test_unit_addition!(rad, grad, "1.0157079633"); +test_unit_addition!(rad, rad, "2"); +test_unit_addition!(rad, turn, "7.2831853072"); + +test_unit_addition!(turn, deg, "1.0027777778"); +test_unit_addition!(turn, grad, "1.0025"); +test_unit_addition!(turn, rad, "1.1591549431"); +test_unit_addition!(turn, turn, "2"); + +test_unit_addition!(s, s, "2"); +test_unit_addition!(s, ms, "1.001"); + +test_unit_addition!(ms, s, "1001"); +test_unit_addition!(ms, ms, "2"); + +test_unit_addition!(Hz, Hz, "2"); +test_unit_addition!(Hz, kHz, "1001"); + +test_unit_addition!(kHz, Hz, "1.001"); +test_unit_addition!(kHz, kHz, "2"); + +test_unit_addition!(dpi, dpi, "2"); +test_unit_addition!(dpi, dpcm, "3.54"); +test_unit_addition!(dpi, dppx, "97"); + +test_unit_addition!(dpcm, dpi, "1.3937007874"); +test_unit_addition!(dpcm, dpcm, "2"); +test_unit_addition!(dpcm, dppx, "38.7952755906"); + +test_unit_addition!(dppx, dpi, "1.0104166667"); +test_unit_addition!(dppx, dpcm, "1.0264583333"); +test_unit_addition!(dppx, dppx, "2"); diff --git a/css/parser/tests/grass_unknown_at_rule.rs b/css/parser/tests/grass_unknown_at_rule.rs new file mode 100644 index 000000000000..a082bb1486bc --- /dev/null +++ b/css/parser/tests/grass_unknown_at_rule.rs @@ -0,0 +1,30 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + basic_unknown_at_rule, + "@foo {\n a {\n color: red;\n }\n}\n" +); +grass_test!(unknown_at_rule_no_selector, "@foo {\n color: red;\n}\n"); +grass_test!(unknown_at_rule_no_body, "@foo;\n"); +grass_test!(unknown_at_rule_no_body_eof, "@foo", "@foo;\n"); +grass_test!( + unknown_at_rule_interpolated_eof_no_body, + "@#{()if(0,0<0,0)}", + "@false;\n" +); +grass_test!(nothing_after_hash, "@foo #", "@foo #;\n"); +grass_test!( + style_following, + "@foo (a: b) { + a { + color: red; + } + } + + a { + color: green; + }", + "@foo (a: b) {\n a {\n color: red;\n }\n}\na {\n color: green;\n}\n" +); +grass_test!(contains_multiline_comment, "@foo /**/;\n", "@foo;\n"); diff --git a/css/parser/tests/grass_url.rs b/css/parser/tests/grass_url.rs new file mode 100644 index 000000000000..64df475ec961 --- /dev/null +++ b/css/parser/tests/grass_url.rs @@ -0,0 +1,166 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + arithmetic_both_space, + "a {\n color: url(1 + 2);\n}\n", + "a {\n color: url(3);\n}\n" +); +grass_test!( + arithmetic_space_right, + "a {\n color: url(1+ 2);\n}\n", + "a {\n color: url(3);\n}\n" +); +grass_test!( + arithmetic_space_left, + "a {\n color: url(1 +2);\n}\n", + "a {\n color: url(3);\n}\n" +); +grass_test!( + arithmetic_no_space, + "a {\n color: url(1+2);\n}\n", + "a {\n color: url(1+2);\n}\n" +); +grass_test!( + arithmetic_space_start_of_url, + "a {\n color: url( 1+2);\n}\n", + "a {\n color: url(1+2);\n}\n" +); +grass_test!( + arithmetic_space_end_of_url, + "a {\n color: url(1+2 );\n}\n", + "a {\n color: url(1+2);\n}\n" +); +grass_test!( + arithmetic_space_start_end_of_url, + "a {\n color: url( 1+2 );\n}\n", + "a {\n color: url(1+2);\n}\n" +); +grass_test!( + arithmetic_space_start_end_of_url_and_operands, + "a {\n color: url( 1 + 2 );\n}\n", + "a {\n color: url(3);\n}\n" +); +grass_test!( + silent_comment, + "a {\n color: url(//some/absolute/path);\n}\n" +); +grass_test!( + multiline_comment, + "a {\n color: url(/*looks-like-a*/comment);\n}\n" +); +grass_test!(plain_css_function, "a {\n color: url(fn(\"s\"));\n}\n"); +grass_test!( + builtin_function, + "a {\n color: url(if(true, \"red.png\", \"blue.png\"));\n}\n", + "a {\n color: url(\"red.png\");\n}\n" +); +grass_test!( + user_defined_function, + "$file-1x: \"budge.png\";\n@function fudge($str) {\n @return \"assets/fudge/\"+$str;\n}\n\na \ + {\n color: url(fudge(\"#{$file-1x}\"));\n}\n", + "a {\n color: url(\"assets/fudge/budge.png\");\n}\n" +); +grass_test!( + unquoted_interpolation, + "a {\n color: url(hello-#{world}.png);\n}\n", + "a {\n color: url(hello-world.png);\n}\n" +); +grass_test!( + quoted_interpolation, + "a {\n color: url(\"hello-#{world}.png\");\n}\n", + "a {\n color: url(\"hello-world.png\");\n}\n" +); +grass_test!(simple_forward_slash, "a {\n color: url(foo/bar.css);\n}\n"); +grass_test!(http_url, "a {\n color: url(http://foo.bar.com);\n}\n"); +grass_test!( + google_fonts_url, + "a {\n color: url(http://fonts.googleapis.com/css?family=Karla:400,700,400italic|Anonymous+Pro:400,700,400italic);\n}\n" +); +grass_test!( + interpolation_in_http_url, + "a {\n color: url(http://blah.com/bar-#{foo}.css);\n}\n", + "a {\n color: url(http://blah.com/bar-foo.css);\n}\n" +); +grass_test!( + many_forward_slashes, + "a {\n color: url(http://box_////fudge.css);\n}\n" +); +grass_test!( + url_whitespace, + "a {\n color: url( 1 );\n}\n", + "a {\n color: url(1);\n}\n" +); +grass_test!( + url_newline, + "a {\n color: url(\n);\n}\n", + "a {\n color: url();\n}\n" +); +grass_test!(url_comma_list, "a {\n color: url(1, 2, a, b, c);\n}\n"); +grass_test!( + url_contains_only_interpolation, + "a {\n color: url(#{1 + 2});\n}\n", + "a {\n color: url(3);\n}\n" +); +grass_test!( + url_begins_with_interpolation, + "a {\n color: url(#{http}://foo);\n}\n", + "a {\n color: url(http://foo);\n}\n" +); +grass_test!(url_dot_dot, "a {\n color: url(../foo/bar/..baz/);\n}\n"); +grass_test!( + silent_comment_in_interpolation, + "$roboto-font-path: \"../fonts/roboto\";\n\na {\n color: url(#{//}\n \ + $roboto-font-path});\n}\n", + "a {\n color: url(../fonts/roboto);\n}\n" +); +grass_test!( + interpolation_in_nested_url, + "a {\n color: url(url(#{foo}));\n}\n", + "a {\n color: url(url(foo));\n}\n" +); +grass_test!( + no_space_after_colon_and_contains_semicolon, + "a {\n color:url(;);\n}\n", + "a {\n color: url(;);\n}\n" +); +grass_test!( + begins_with_single_forward_slash, + "a {\n color: url(/rust-logo.png);\n}\n", + "a {\n color: url(/rust-logo.png);\n}\n" +); +grass_test!( + url_uppercase, + "a {\n color: URL(http://foo);\n}\n", + "a {\n color: url(http://foo);\n}\n" +); +grass_test!( + url_mixed_casing, + "a {\n color: UrL(http://foo);\n}\n", + "a {\n color: url(http://foo);\n}\n" +); +grass_test!( + url_browser_prefixed, + "a {\n color: -webkit-url(https://google.com);\n}\n", + "a {\n color: url(https://google.com);\n}\n" +); +grass_test!( + url_hash_no_interpolation, + "a {\n color: url(#);\n}\n", + "a {\n color: url(#);\n}\n" +); +grass_error!( + url_nothing_after_forward_slash_in_interpolation, + "a { color: url(#{/", + "Error: Expected expression." +); +grass_error!( + url_nothing_after_backslash_in_interpolation_in_quote, + "a { color: url(#{\"\\", + "Error: Expected \"." +); +grass_error!( + url_nothing_after_hash_in_interpolation_in_quote, + "a { color: url(#{\"#", + "Error: Expected \"." +); diff --git a/css/parser/tests/grass_values.rs b/css/parser/tests/grass_values.rs new file mode 100644 index 000000000000..3c1f5867d29e --- /dev/null +++ b/css/parser/tests/grass_values.rs @@ -0,0 +1,86 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + single_quote, + "a {\n color: 'foo';\n}\n", + "a {\n color: \"foo\";\n}\n" +); +grass_test!(double_quote, "a {\n color: \"foo\";\n}\n"); +grass_test!(comma_list_ident, "a {\n color: foo, bar, baz;\n}\n"); +grass_test!(space_list_ident, "a {\n color: foo bar baz;\n}\n"); +grass_test!(comma_list_number, "a {\n color: 1, 2, 3;\n}\n"); +grass_test!(space_list_number, "a {\n color: 1 2 3;\n}\n"); +grass_test!(comma_space_list_number, "a {\n color: 1 1, 2 2, 3 3;\n}\n"); +grass_test!(preserves_keyword_true, "a {\n color: true;\n}\n"); +grass_test!(preserves_keyword_false, "a {\n color: false;\n}\n"); +grass_test!( + does_not_preserve_keyword_null, + "a {\n color: null;\n}\n", + "" +); +grass_test!(preserves_keyword_auto, "a {\n color: auto;\n}\n"); +grass_test!(preserves_keyword_initial, "a {\n color: initial;\n}\n"); +grass_test!(preserves_keyword_infinity, "a {\n color: infinity;\n}\n"); +grass_error!( + keyword_not_expects_expression, + "a {\n color: not;\n}\n", + "Error: Expected expression." +); +grass_test!(preserves_keyword_and, "a {\n color: and;\n}\n"); +grass_test!(preserves_keyword_or, "a {\n color: or;\n}\n"); +grass_test!(preserves_keyword_unset, "a {\n color: unset;\n}\n"); +grass_test!(preserves_keyword_nan, "a {\n color: NaN;\n}\n"); +grass_test!(preserves_keyword_from, "a {\n color: FRoM;\n}\n"); +grass_test!(preserves_keyword_to, "a {\n color: To;\n}\n"); +grass_test!(preserves_keyword_through, "a {\n color: ThRouGh;\n}\n"); +grass_test!( + preserves_quotes, + "a {\n color: \"'foo' \\\"bar\\\"\";\n}\n" +); +grass_test!( + whitespace_space_list_number, + "a {\n color: 1 2 3 ;\n}\n", + "a {\n color: 1 2 3;\n}\n" +); +grass_test!( + whitespace_comma_list_number, + "a {\n color: 1 , 2 , 3 ;\n}\n", + "a {\n color: 1, 2, 3;\n}\n" +); +grass_test!(number, "a {\n color: 1;\n}\n"); +grass_test!( + removes_paren_around_single_value, + "a {\n color: (foo);\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + undefined_function_call_is_ident, + "a {\n color: foo();\n}\n" +); +grass_test!(hash_identifier_is_not_color, "a {\n color: #foo;\n}\n"); +grass_test!( + hash_identifier_is_string, + "a {\n color: type-of(#foo);\n}\n", + "a {\n color: string;\n}\n" +); +grass_test!( + adjacent_strings_get_spaced, + "a {\n color: \"f\"foo;\n}\n", + "a {\n color: \"f\" foo;\n}\n" +); +grass_test!( + many_parens, + "a {\n color: (((((red)))));\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + negative_number_times_number, + "a {\n color: -1 * 2;\n}\n", + "a {\n color: -2;\n}\n" +); +grass_error!( + value_missing_closing_paren, + "a {\n color: (red;\n}\n", + "Error: expected \")\"." +); diff --git a/css/parser/tests/grass_variables.rs b/css/parser/tests/grass_variables.rs new file mode 100644 index 000000000000..14b9daab6b46 --- /dev/null +++ b/css/parser/tests/grass_variables.rs @@ -0,0 +1,414 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + basic_variable, + "$height: 1px;\na {\n height: $height;\n}\n", + "a {\n height: 1px;\n}\n" +); +grass_test!( + variable_redeclaration, + "$a: 1px;\n$a: 2px;\na {\n height: $a;\n}\n", + "a {\n height: 2px;\n}\n" +); +grass_test!( + variable_shadowing, + "$a: 1px;\n$b: $a;\na {\n height: $b;\n}\n", + "a {\n height: 1px;\n}\n" +); +grass_test!( + variable_shadowing_val_does_not_change, + "$a: 1px;\n$b: $a; $a: 2px;\na {\n height: $b;\n}\n", + "a {\n height: 1px;\n}\n" +); +grass_test!( + variable_shadowing_val_does_not_change_complex, + "a {\n color: red;\n}\n$y: before;\n$x: 1 2 $y;\n$y: after;\nfoo {\n a: $x;\n}", + "a {\n color: red;\n}\n\nfoo {\n a: 1 2 before;\n}\n" +); +grass_test!( + variable_whitespace, + "$a : 1px ;\na {\n height: $a;\n}\n", + "a {\n height: 1px;\n}\n" +); +grass_test!( + style_after_variable, + "$a: 1px;\na {\n height: $a;\n color: red;\n}\n", + "a {\n height: 1px;\n color: red;\n}\n" +); +grass_test!( + literal_and_variable_as_val, + "$a: 1px;\na {\n height: 1 $a;\n}\n", + "a {\n height: 1 1px;\n}\n" +); +grass_test!( + literal_and_variable_as_var, + "$a: 1px;\n$b: 1 $a;\na {\n height: $b;\n}\n", + "a {\n height: 1 1px;\n}\n" +); +grass_test!( + eats_whitespace_after_variable_value, + "a {\n b {\n $c: red;\n }\n color: red;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + variable_changes_through_new_ruleset, + "a {\n $c: red;\nb {\n $c: blue;\n }\n color: $c;\n}\n", + "a {\n color: blue;\n}\n" +); +grass_test!( + nested_interpolation, + "$a: red; a {\n color: #{#{$a}};\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + numbers_in_variable, + "$var1: red; a {\n color: $var1;\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + default_var_after, + "$a: red;\n$a: blue !default;\na {\n color: $a;\n}", + "a {\n color: red;\n}\n" +); +grass_test!( + default_var_before, + "$a: red !default;\n$a: blue;\na {\n color: $a;\n}", + "a {\n color: blue;\n}\n" +); +grass_test!( + default_var_whitespace, + "$a: red !default ;\na {\n color: $a;\n}", + "a {\n color: red;\n}\n" +); +grass_test!( + default_var_inside_rule, + "a {\n $a: red;\n $a: blue !default;\n color: $a;\n}", + "a {\n color: red;\n}\n" +); +grass_test!( + interpolation_in_variable, + "$a: #{red};\na {\n color: $a\n}\n", + "a {\n color: red;\n}\n" +); +grass_test!( + variable_decl_doesnt_end_in_semicolon, + "a {\n $a: red\n}\n\nb {\n color: blue;\n}\n", + "b {\n color: blue;\n}\n" +); +grass_test!( + unicode_in_variables, + "$vär: foo;\na {\n color: $vär;\n}\n", + "a {\n color: foo;\n}\n" +); +grass_test!( + variable_does_not_include_interpolation, + "$input: foo;\na {\n color: $input#{\"literal\"};\n}\n", + "a {\n color: foo literal;\n}\n" +); +grass_test!( + whitespace_after_variable_name_in_declaration, + "a {\n $x : true;\n color: $x;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + important_in_variable, + "$a: !important;\n\na {\n color: $a;\n}\n", + "a {\n color: !important;\n}\n" +); +grass_test!( + important_in_variable_casing, + "$a: !ImPoRtAnT;\n\na {\n color: $a;\n}\n", + "a {\n color: !important;\n}\n" +); +grass_test!( + exclamation_in_quoted_string, + "$a: \"big bang!\";\n\na {\n color: $a;\n}\n", + "a {\n color: \"big bang!\";\n}\n" +); +grass_test!( + flag_uses_escape_sequence, + "$a: red;\n\na {\n $a: green !\\67 lobal;\n}\n\na {\n color: $a;\n}\n", + "a {\n color: green;\n}\n" +); +grass_test!( + not_equal_in_variable_decl, + "$a: red != blue;\n\na {\n color: $a;\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + error_in_default_value_already_set_is_ignored, + "$a: red; + + $a: hue(\"not a color, should error\") !default; + + a { + color: $a; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + multiline_comments_everywhere, + " /**/ $a /**/ : /**/ red /**/ ; /**/ ", + "/**/\n/**/\n" +); +grass_test!( + default_var_overrides_when_null_declared_global, + "$a: null; + $a: red !default; + + a { + color: $a; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + default_var_overrides_when_null_declared_local, + "a { + $a: null; + $a: red !default; + + color: $a; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + default_var_overrides_when_null_declared_local_with_global_flags, + "a { + $a: null !global; + $a: red !default !global; + + color: $a; + }", + "a {\n color: red;\n}\n" +); +grass_test!( + default_at_root_inside_control_flow, + "$a: initial; + + @if true { + $a: outer !default; + } + + a { + color: $a; + }", + "a {\n color: initial;\n}\n" +); +grass_test!( + default_at_root_inside_control_flow_outer_is_null, + "$a: null; + + @if true { + $a: outer !default; + } + + a { + color: $a; + }", + "a {\n color: outer;\n}\n" +); +grass_test!( + variable_declared_at_root_inside_if, + "@if true { + $a: outer; + } + + a { + color: variable-exists(a); + }", + "a {\n color: false;\n}\n" +); +grass_test!( + variable_declared_at_root_inside_if_default, + "@if true { + $a: outer !default; + } + + a { + color: variable-exists(a); + }", + "a {\n color: false;\n}\n" +); +grass_test!( + variable_declared_at_root_inside_if_global, + "@if true { + $a: outer !global; + } + + a { + color: variable-exists(a); + }", + "a {\n color: true;\n}\n" +); +grass_test!( + variable_declared_at_root_and_globally_inside_if_default, + "$a: null; + + @if true { + $a: null; + $a: outer !default; + + a { + color: $a; + } + } + + a { + color: $a; + }", + "a {\n color: outer;\n}\n\na {\n color: outer;\n}\n" +); +grass_test!( + global_inside_style_inside_control_flow_declared_outer, + "$y: a; + + a { + $y: b; + + @if true { + $y: c !global; + } + + color: $y; + }", + "a {\n color: b;\n}\n" +); +grass_test!( + inside_style_inside_control_flow_declared_outer, + "$y: a; + + a { + $y: b; + + @if true { + $y: c; + } + + color: $y; + }", + "a {\n color: c;\n}\n" +); +grass_test!( + inside_style_inside_control_flow_declared_outer_global_comes_prior, + "$a: a; + + a { + $a: b; + + @if true { + $a: c !global; + color: $a; + $a: e; + } + + color: $a; + }", + "a {\n color: b;\n color: e;\n}\n" +); +// https://github.com/Kixiron/lasso/issues/7 +grass_test!( + regression_test_for_lasso_0_3_0, + "$a: foo; + $b: foo; + $c: foo; + $d: foo; + $e: foo; + $f: foo; + $g: foo; + $h: foo; + $i: foo; + $j: foo; + $k: foo; + $l: foo; + $m: foo; + $n: foo; + $o: foo; + $p: foo; + $q: foo; + $r: foo; + $s: foo; + $t: foo; + $u: foo; + $v: foo; + $w: foo; + $x: foo; + $y: foo; + $z: foo; + $aa: foo; + $bb: foo; + $cc: foo; + $dd: foo; + $ee: foo; + $ff: foo; + $gg: foo; + $hh: foo; + $ii: foo; + $jj: foo; + $kk: foo; + $ll: foo; + $mm: foo; + $nn: foo; + $oo: foo; + $pp: foo; + $qq: foo; + $rr: foo; + $ss: foo; + $tt: foo; + $uu: foo; + $vv: foo; + $ww: foo; + $xx: foo; + $yy: foo; + $zz: foo; + $aaa: foo; + $bbb: foo; + $ccc: foo; + + $global-inverse-color: #fff; + + $inverse-global-muted-color: $global-inverse-color; + a { + color: $inverse-global-muted-color; + } + ", + "a {\n color: #fff;\n}\n" +); +grass_error!(ends_with_bang, "$a: red !;", "Error: Expected identifier."); +grass_error!(unknown_flag, "$a: red !foo;", "Error: Invalid flag name."); +grass_error!( + flag_in_middle_of_value, + "$a: a !default b;", + "Error: expected \";\"." +); +// note: dart-sass expects !important +grass_error!( + no_value_only_flag, + "$a: !default;", + "Error: Expected expression." +); +grass_error!( + uppercase_flag, + "$a: 1 !GLOBAL;", + "Error: Invalid flag name." +); +grass_error!( + undefined_variable, + "a {color: $a;}", + "Error: Undefined variable." +); +grass_error!( + invalid_escape, + "$\\110000: red;", + "Error: Invalid escape sequence." +); +grass_error!( + nothing_after_hash_in_variable_decl, + "$color: #", + "Error: Expected identifier." +); +grass_error!( + only_semicolon_after_hash_in_variable_decl, + "$color: #;", + "Error: Expected identifier." +); diff --git a/css/parser/tests/grass_while.rs b/css/parser/tests/grass_while.rs new file mode 100644 index 000000000000..57fb01fbc9dc --- /dev/null +++ b/css/parser/tests/grass_while.rs @@ -0,0 +1,141 @@ +#[macro_use] +mod grass_macros; + +grass_test!( + inner_increment_var, + "$a: 4;\n$b: 1;\na {\n @while $a > $b {\n color: $b;\n $b: $b + 1;\n }\n}", + "a {\n color: 1;\n color: 2;\n color: 3;\n}\n" +); +grass_test!( + outer_increment_var, + "$a: 4;\n$b: 1;\n@while $a > $b {\na {\n color: $b;\n }\n $b: $b + 1;\n}", + "a {\n color: 1;\n}\n\na {\n color: 2;\n}\n\na {\n color: 3;\n}\n" +); +grass_test!( + inner_while_false, + "a {\n @while false {\n color: foo;\n }\n}", + "" +); +grass_test!( + outer_while_false, + "@while false {\na {\n color: $b;\n }\n $b: $b + 1;\n}", + "" +); +grass_error!( + while_empty_condition, + "@while {}", + "Error: Expected expression." +); +grass_test!( + early_return_in_function, + "@function bar() {\n @while (true) {\n @return true;\n }\n}\n\na {\n color: bar();\n}\n", + "a {\n color: true;\n}\n" +); +grass_test!( + nested_while_at_root_scope, + "$continue_inner: true;\n$continue_outer: true;\n\n@while $continue_outer {\n @while \ + $continue_inner {\n $continue_inner: false;\n }\n\n $continue_outer: \ + false;\n}\n\nresult {\n continue_outer: $continue_outer;\n continue_inner: \ + $continue_inner;\n}\n", + "result {\n continue_outer: false;\n continue_inner: false;\n}\n" +); +grass_test!( + nested_while_not_at_root_scope, + "$continue_inner: true;\n$continue_outer: true;\n\nresult {\n @while $continue_outer {\n \ + @while $continue_inner {\n $continue_inner: false;\n }\n\n $continue_outer: \ + false;\n }\n\n continue_outer: $continue_outer;\n continue_inner: $continue_inner;\n}\n", + "result {\n continue_outer: true;\n continue_inner: true;\n}\n" +); +grass_test!( + local_scope_at_root, + "$continue_inner: true; + $continue_outer: true; + + @while $continue_outer { + $local_implicit: outer; + $local_explicit: outer !global; + $local_default: outer !default; + + @while $continue_inner { + $local_implicit: inner; + $local_explicit: inner !global; + $local_default: inner !default; + $continue_inner: false; + } + + $continue_outer: false; + } + + result { + @if variable-exists(local_default) { + local_default: $local_default; + } + + @if variable-exists(local_implicit) { + local_implicit: $local_implicit; + } + + @if variable-exists(local_explicit) { + local_explicit: $local_explicit; + } + }", + "result {\n local_explicit: inner;\n}\n" +); +grass_test!( + global_scope_at_root, + "$continue_inner: true; + $continue_outer: true; + $root_default: initial; + $root_implicit: initial; + $root_explicit: initial !global; + + @while $continue_outer { + $root_implicit: outer; + $root_explicit: outer !global; + $root_default: outer !default; + + @while $continue_inner { + $root_implicit: inner; + $root_explicit: inner !global; + $root_default: inner !default; + $continue_inner: false; + } + + $continue_outer: false; + } + + result { + root_default: $root_default; + root_implicit: $root_implicit; + root_explicit: $root_explicit; + }", + "result {\n root_default: initial;\n root_implicit: inner;\n root_explicit: inner;\n}\n" +); +grass_test!( + if_inside_while, + "$continue_outer: true; + @while $continue_outer { + a { + color: red; + } + + @if true { + $continue_outer: false; + } + + a { + color: blue; + } + }", + "a {\n color: red;\n}\n\na {\n color: blue;\n}\n" +); +grass_test!( + multiline_comments_everywhere, + " /**/ @while /**/ false /**/ {} /**/ ", + "/**/\n/**/\n" +); +grass_error!( + missing_closing_curly_brace, + "@while true {", + "Error: expected \"}\"." +); diff --git a/css/src/lib.rs b/css/src/lib.rs new file mode 100644 index 000000000000..31e1bb209f98 --- /dev/null +++ b/css/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}