2020-01-04 22:55:04 -05:00
#![ warn(
clippy ::all ,
clippy ::restriction ,
clippy ::pedantic ,
clippy ::nursery ,
// clippy::cargo
) ]
#![ deny(missing_debug_implementations) ]
#![ allow(
dead_code ,
clippy ::pub_enum_variant_names ,
clippy ::implicit_return ,
clippy ::use_self ,
clippy ::missing_docs_in_private_items ,
clippy ::todo ,
clippy ::dbg_macro ,
clippy ::unreachable ,
clippy ::wildcard_enum_match_arm ,
clippy ::option_expect_used ,
clippy ::panic ,
clippy ::unused_self ,
clippy ::too_many_lines ,
clippy ::integer_arithmetic ,
2020-01-11 14:51:31 -05:00
clippy ::missing_errors_doc ,
2020-01-11 16:12:23 -05:00
clippy ::let_underscore_must_use ,
clippy ::module_name_repetitions
2020-01-04 22:55:04 -05:00
) ]
use std ::collections ::HashMap ;
2020-01-06 17:06:37 -05:00
use std ::fmt ::{ self , Display } ;
2020-01-04 22:55:04 -05:00
use std ::fs ;
use std ::io ;
2020-01-06 17:06:37 -05:00
use std ::iter ::{ Iterator , Peekable } ;
2020-01-07 19:58:38 -05:00
use std ::path ::Path ;
2020-01-04 22:55:04 -05:00
2020-01-07 18:37:28 -05:00
use crate ::common ::{ AtRule , Keyword , Op , Pos , Symbol , Whitespace } ;
2020-01-05 12:45:51 -05:00
use crate ::css ::Css ;
2020-01-06 18:55:51 -05:00
use crate ::error ::SassError ;
2020-01-04 22:55:04 -05:00
use crate ::format ::PrettyPrinter ;
use crate ::lexer ::Lexer ;
2020-01-11 14:51:31 -05:00
use crate ::selector ::{ Attribute , Selector } ;
2020-01-06 16:50:16 -05:00
use crate ::style ::Style ;
2020-01-04 22:55:04 -05:00
use crate ::units ::Unit ;
mod color ;
mod common ;
2020-01-05 12:45:51 -05:00
mod css ;
2020-01-04 22:55:04 -05:00
mod error ;
mod format ;
2020-01-11 18:42:42 -05:00
mod imports ;
2020-01-04 22:55:04 -05:00
mod lexer ;
mod selector ;
2020-01-06 16:50:16 -05:00
mod style ;
2020-01-04 22:55:04 -05:00
mod units ;
2020-01-06 18:55:51 -05:00
type SassResult < T > = Result < T , SassError > ;
2020-01-08 20:39:05 -05:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
pub struct Token {
pos : Pos ,
pub kind : TokenKind ,
}
2020-01-04 22:55:04 -05:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
pub enum TokenKind {
Ident ( String ) ,
Symbol ( Symbol ) ,
Function ( String , Vec < String > ) ,
2020-01-07 18:37:28 -05:00
AtRule ( AtRule ) ,
2020-01-04 22:55:04 -05:00
Keyword ( Keyword ) ,
Number ( String ) ,
Unit ( Unit ) ,
Whitespace ( Whitespace ) ,
Variable ( String ) ,
2020-01-11 14:51:31 -05:00
Attribute ( Attribute ) ,
2020-01-04 22:55:04 -05:00
Style ( Vec < Token > ) ,
2020-01-06 00:40:29 -05:00
Op ( Op ) ,
2020-01-05 19:21:58 -05:00
MultilineComment ( String ) ,
}
impl Display for TokenKind {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
match self {
2020-01-07 18:37:28 -05:00
TokenKind ::Ident ( s ) | TokenKind ::Number ( s ) = > write! ( f , " {} " , s ) ,
2020-01-05 19:21:58 -05:00
TokenKind ::Symbol ( s ) = > write! ( f , " {} " , s ) ,
2020-01-07 18:37:28 -05:00
TokenKind ::AtRule ( s ) = > write! ( f , " {} " , s ) ,
2020-01-06 00:40:29 -05:00
TokenKind ::Op ( s ) = > write! ( f , " {} " , s ) ,
2020-01-05 19:21:58 -05:00
TokenKind ::Unit ( s ) = > write! ( f , " {} " , s ) ,
TokenKind ::Whitespace ( s ) = > write! ( f , " {} " , s ) ,
2020-01-11 14:51:31 -05:00
TokenKind ::Attribute ( s ) = > write! ( f , " {} " , s ) ,
2020-01-05 19:21:58 -05:00
TokenKind ::Function ( name , args ) = > write! ( f , " {}({}) " , name , args . join ( " , " ) ) ,
TokenKind ::Keyword ( kw ) = > write! ( f , " {} " , kw ) ,
TokenKind ::MultilineComment ( s ) = > write! ( f , " /*{}*/ " , s ) ,
TokenKind ::Variable ( s ) = > write! ( f , " ${} " , s ) ,
TokenKind ::Style ( _ ) = > panic! ( " TokenKind should not be used to format styles " ) ,
}
}
2020-01-04 22:55:04 -05:00
}
2020-01-08 20:58:02 -05:00
/// Represents a parsed SASS stylesheet with nesting
2020-01-04 22:55:04 -05:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub struct StyleSheet {
rules : Vec < Stmt > ,
}
#[ derive(Clone, Debug, Eq, PartialEq) ]
pub enum Stmt {
2020-01-09 20:56:09 -05:00
/// A [`Style`](/grass/style/struct.Style)
2020-01-04 22:55:04 -05:00
Style ( Style ) ,
2020-01-09 20:56:09 -05:00
/// A [`RuleSet`](/grass/struct.RuleSet.html)
2020-01-04 22:55:04 -05:00
RuleSet ( RuleSet ) ,
2020-01-08 20:58:02 -05:00
/// A multiline comment: `/* foo bar */`
2020-01-08 20:39:05 -05:00
MultilineComment ( String ) ,
2020-01-04 22:55:04 -05:00
}
2020-01-08 20:58:02 -05:00
/// Represents a single rule set. Rule sets can contain other rule sets
2020-01-11 14:51:31 -05:00
///
2020-01-08 20:58:02 -05:00
/// a {
/// color: blue;
/// b {
/// color: red;
/// }
/// }
2020-01-04 22:55:04 -05:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
pub struct RuleSet {
selector : Selector ,
rules : Vec < Stmt > ,
// potential optimization: we don't *need* to own the selector
2020-01-05 12:37:50 -05:00
super_selector : Selector ,
2020-01-04 22:55:04 -05:00
}
2020-01-08 20:58:02 -05:00
/// An intermediate representation of what are essentially single lines
/// todo! rename this
2020-01-04 22:55:04 -05:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
enum Expr {
2020-01-08 20:58:02 -05:00
/// A style: `color: red`
2020-01-04 22:55:04 -05:00
Style ( Style ) ,
2020-01-08 20:58:02 -05:00
/// A full selector `a > h1`
2020-01-04 22:55:04 -05:00
Selector ( Selector ) ,
2020-01-08 20:58:02 -05:00
/// A variable declaration `$var: 1px`
2020-01-05 19:21:58 -05:00
VariableDecl ( String , Vec < Token > ) ,
2020-01-08 20:58:02 -05:00
/// A multiline comment: `/* foobar */`
2020-01-08 20:39:05 -05:00
MultilineComment ( String ) ,
2020-01-04 22:55:04 -05:00
}
impl StyleSheet {
2020-01-06 18:55:51 -05:00
pub fn new ( input : & str ) -> SassResult < StyleSheet > {
2020-01-04 22:55:04 -05:00
StyleSheetParser {
2020-01-05 19:21:58 -05:00
global_variables : HashMap ::new ( ) ,
2020-01-04 22:55:04 -05:00
lexer : Lexer ::new ( input ) . peekable ( ) ,
rules : Vec ::new ( ) ,
2020-01-06 18:55:51 -05:00
scope : 0 ,
2020-01-07 19:24:37 -05:00
file : String ::from ( " stdin " ) ,
}
. parse_toplevel ( )
}
pub fn from_path < P : AsRef < Path > + Into < String > > ( p : P ) -> SassResult < StyleSheet > {
StyleSheetParser {
global_variables : HashMap ::new ( ) ,
lexer : Lexer ::new ( & fs ::read_to_string ( p . as_ref ( ) ) ? ) . peekable ( ) ,
rules : Vec ::new ( ) ,
scope : 0 ,
file : p . into ( ) ,
2020-01-04 22:55:04 -05:00
}
. parse_toplevel ( )
}
2020-01-08 20:58:02 -05:00
/// Print the internal representation of a parsed stylesheet
2020-01-11 14:51:31 -05:00
///
2020-01-08 20:58:02 -05:00
/// Very closely resembles the origin SASS, but contains only things translatable
/// to pure CSS
2020-01-11 14:51:31 -05:00
///
2020-01-08 20:58:02 -05:00
/// Used mainly in debugging, but can at times be useful
2020-01-04 22:55:04 -05:00
pub fn pretty_print < W : std ::io ::Write > ( & self , buf : W ) -> io ::Result < ( ) > {
PrettyPrinter ::new ( buf ) . pretty_print ( self )
}
2020-01-05 12:37:02 -05:00
2020-01-08 20:58:02 -05:00
fn pretty_print_selectors < W : std ::io ::Write > ( & self , buf : W ) -> io ::Result < ( ) > {
2020-01-05 12:37:02 -05:00
PrettyPrinter ::new ( buf ) . pretty_print_preserve_super_selectors ( self )
}
2020-01-08 20:58:02 -05:00
/// Write the internal representation as CSS to `buf`
2020-01-05 12:37:02 -05:00
pub fn print_as_css < W : std ::io ::Write > ( self , buf : & mut W ) -> io ::Result < ( ) > {
Css ::from_stylesheet ( self ) . pretty_print ( buf )
}
}
2020-01-04 22:55:04 -05:00
#[ derive(Debug, Clone) ]
struct StyleSheetParser < ' a > {
2020-01-05 19:21:58 -05:00
global_variables : HashMap < String , Vec < Token > > ,
2020-01-04 22:55:04 -05:00
lexer : Peekable < Lexer < ' a > > ,
rules : Vec < Stmt > ,
2020-01-06 18:55:51 -05:00
scope : u32 ,
2020-01-07 19:24:37 -05:00
file : String ,
2020-01-04 22:55:04 -05:00
}
impl < ' a > StyleSheetParser < ' a > {
2020-01-07 19:24:37 -05:00
fn parse_toplevel ( mut self ) -> SassResult < StyleSheet > {
2020-01-04 22:55:04 -05:00
let mut rules = Vec ::new ( ) ;
2020-01-07 18:37:28 -05:00
while let Some ( Token { kind , .. } ) = self . lexer . peek ( ) {
match kind . clone ( ) {
2020-01-04 22:55:04 -05:00
TokenKind ::Ident ( _ )
2020-01-11 14:51:31 -05:00
| TokenKind ::Attribute ( _ )
2020-01-04 22:55:04 -05:00
| TokenKind ::Symbol ( Symbol ::Hash )
| TokenKind ::Symbol ( Symbol ::Colon )
| TokenKind ::Symbol ( Symbol ::Mul )
2020-01-11 14:51:31 -05:00
| TokenKind ::Symbol ( Symbol ::Period ) = > rules . extend (
self . eat_rules ( & Selector ( Vec ::new ( ) ) , & mut self . global_variables . clone ( ) ) ,
) ,
2020-01-04 22:55:04 -05:00
TokenKind ::Whitespace ( _ ) | TokenKind ::Symbol ( _ ) = > {
self . lexer . next ( ) ;
continue ;
}
2020-01-05 19:21:58 -05:00
TokenKind ::Variable ( name ) = > {
2020-01-07 19:24:37 -05:00
let Token { ref pos , .. } = self
2020-01-07 18:37:28 -05:00
. lexer
. next ( )
. expect ( " this cannot occur as we have already peeked " ) ;
2020-01-05 19:21:58 -05:00
self . devour_whitespace ( ) ;
2020-01-07 19:58:38 -05:00
if self
2020-01-05 19:21:58 -05:00
. lexer
. next ( )
2020-01-07 18:37:28 -05:00
. unwrap_or_else ( | | self . error ( pos , " expected value after variable " ) )
2020-01-05 19:21:58 -05:00
. kind
! = TokenKind ::Symbol ( Symbol ::Colon )
{
2020-01-07 18:37:28 -05:00
self . error ( pos , " unexpected variable use at toplevel " ) ;
2020-01-05 19:21:58 -05:00
}
let val = self . eat_variable_value ( ) ;
self . global_variables . insert ( name , val ) ;
}
2020-01-07 19:58:38 -05:00
TokenKind ::MultilineComment ( comment ) = > {
2020-01-08 20:39:05 -05:00
self . lexer . next ( ) ;
rules . push ( Stmt ::MultilineComment ( comment ) ) ;
2020-01-07 19:58:38 -05:00
}
2020-01-07 18:37:28 -05:00
TokenKind ::AtRule ( _ ) = > self . eat_at_rule ( ) ,
2020-01-04 22:55:04 -05:00
_ = > todo! ( " unexpected toplevel token " ) ,
} ;
}
2020-01-06 19:23:27 -05:00
Ok ( StyleSheet { rules } )
2020-01-04 22:55:04 -05:00
}
2020-01-07 18:37:28 -05:00
fn eat_at_rule ( & mut self ) {
if let Some ( Token {
2020-01-07 19:24:37 -05:00
kind : TokenKind ::AtRule ( ref rule ) ,
ref pos ,
2020-01-07 18:37:28 -05:00
} ) = self . lexer . next ( )
{
match rule {
AtRule ::Error = > {
2020-01-07 19:24:37 -05:00
self . devour_whitespace ( ) ;
2020-01-07 18:37:28 -05:00
let message = self
. lexer
. by_ref ( )
. take_while ( | x | x . kind ! = TokenKind ::Symbol ( Symbol ::SemiColon ) )
. map ( | x | x . kind . to_string ( ) )
. collect ::< String > ( ) ;
self . error ( pos , & message ) ;
}
_ = > todo! ( " encountered unimplemented at rule " ) ,
}
}
}
2020-01-07 19:24:37 -05:00
fn error ( & self , pos : & Pos , message : & str ) -> ! {
2020-01-07 18:37:28 -05:00
eprintln! ( " Error: {} " , message ) ;
eprintln! (
2020-01-07 19:24:37 -05:00
" {} {}:{} scope on line {} at column {} " ,
self . file ,
2020-01-07 18:37:28 -05:00
pos . line ( ) ,
pos . column ( ) ,
pos . line ( ) ,
pos . column ( )
) ;
2020-01-07 19:58:38 -05:00
let padding = vec! [ ' ' ; format! ( " {} " , pos . line ( ) ) . len ( ) + 1 ]
. iter ( )
. collect ::< String > ( ) ;
2020-01-07 18:37:28 -05:00
eprintln! ( " {} | " , padding ) ;
eprint! ( " {} | " , pos . line ( ) ) ;
2020-01-07 19:24:37 -05:00
eprintln! ( " todo! get line to print as error " ) ;
2020-01-07 19:58:38 -05:00
eprintln! (
" {}| {}^ " ,
padding ,
vec! [ ' ' ; pos . column ( ) as usize ] . iter ( ) . collect ::< String > ( )
) ;
2020-01-07 18:37:28 -05:00
eprintln! ( " {} | " , padding ) ;
std ::process ::exit ( 1 ) ;
}
2020-01-05 19:21:58 -05:00
fn eat_variable_value ( & mut self ) -> Vec < Token > {
self . devour_whitespace ( ) ;
2020-01-06 19:23:27 -05:00
let iter1 = self
. lexer
2020-01-05 19:21:58 -05:00
. by_ref ( )
. take_while ( | x | x . kind ! = TokenKind ::Symbol ( Symbol ::SemiColon ) )
2020-01-06 19:23:27 -05:00
. collect ::< Vec < Token > > ( ) ;
let mut iter2 = Vec ::with_capacity ( iter1 . len ( ) ) ;
for tok in iter1 {
2020-01-07 19:58:38 -05:00
if let Token {
kind : TokenKind ::Variable ( ref name ) ,
ref pos ,
} = tok
{
2020-01-06 19:23:27 -05:00
iter2 . extend (
self . global_variables
2020-01-07 19:24:37 -05:00
. get ( name )
. unwrap_or_else ( | | self . error ( pos , " Undefined variable " ) )
2020-01-06 19:23:27 -05:00
. clone ( ) ,
) ;
} else {
iter2 . push ( tok ) ;
}
}
iter2
2020-01-05 19:21:58 -05:00
}
fn eat_rules (
& mut self ,
super_selector : & Selector ,
vars : & mut HashMap < String , Vec < Token > > ,
) -> Vec < Stmt > {
2020-01-04 22:55:04 -05:00
let mut stmts = Vec ::new ( ) ;
2020-01-11 14:51:31 -05:00
while let Ok ( tok ) = self . eat_expr ( vars , super_selector ) {
2020-01-04 22:55:04 -05:00
match tok {
Expr ::Style ( s ) = > stmts . push ( Stmt ::Style ( s ) ) ,
Expr ::Selector ( s ) = > {
2020-01-06 19:23:27 -05:00
self . scope + = 1 ;
2020-01-05 19:21:58 -05:00
let rules = self . eat_rules ( & super_selector . clone ( ) . zip ( s . clone ( ) ) , vars ) ;
2020-01-04 22:55:04 -05:00
stmts . push ( Stmt ::RuleSet ( RuleSet {
super_selector : super_selector . clone ( ) ,
selector : s ,
rules ,
} ) ) ;
2020-01-06 19:23:27 -05:00
self . scope - = 1 ;
2020-01-07 18:37:28 -05:00
if self . scope = = 0 {
return stmts ;
}
2020-01-04 22:55:04 -05:00
}
2020-01-05 19:21:58 -05:00
Expr ::VariableDecl ( name , val ) = > {
2020-01-06 19:23:27 -05:00
if self . scope = = 0 {
vars . insert ( name . clone ( ) , val . clone ( ) ) ;
self . global_variables . insert ( name , val ) ;
} else {
vars . insert ( name , val ) ;
}
2020-01-05 19:21:58 -05:00
}
2020-01-08 20:39:05 -05:00
Expr ::MultilineComment ( s ) = > stmts . push ( Stmt ::MultilineComment ( s ) ) ,
2020-01-04 22:55:04 -05:00
}
}
stmts
}
2020-01-11 14:51:31 -05:00
fn eat_expr (
& mut self ,
vars : & HashMap < String , Vec < Token > > ,
super_selector : & Selector ,
) -> Result < Expr , ( ) > {
2020-01-04 22:55:04 -05:00
let mut values = Vec ::with_capacity ( 5 ) ;
while let Some ( tok ) = self . lexer . next ( ) {
match tok . kind {
TokenKind ::Symbol ( Symbol ::SemiColon ) | TokenKind ::Symbol ( Symbol ::CloseBrace ) = > {
self . devour_whitespace ( ) ;
2020-01-05 19:21:58 -05:00
return Ok ( Expr ::Style ( Style ::from_tokens ( & values , vars ) ? ) ) ;
2020-01-04 22:55:04 -05:00
}
TokenKind ::Symbol ( Symbol ::OpenBrace ) = > {
self . devour_whitespace ( ) ;
return Ok ( Expr ::Selector ( Selector ::from_tokens (
values . iter ( ) . peekable ( ) ,
2020-01-11 14:51:31 -05:00
super_selector ,
2020-01-04 22:55:04 -05:00
) ) ) ;
}
2020-01-05 19:21:58 -05:00
TokenKind ::Variable ( name ) = > {
2020-01-06 19:23:27 -05:00
if let TokenKind ::Symbol ( Symbol ::Colon ) = self
2020-01-05 19:21:58 -05:00
. lexer
2020-01-06 19:23:27 -05:00
. peek ( )
2020-01-05 19:21:58 -05:00
. expect ( " expected something after variable " )
. kind
{
2020-01-06 19:23:27 -05:00
self . lexer . next ( ) ;
2020-01-05 19:21:58 -05:00
self . devour_whitespace ( ) ;
return Ok ( Expr ::VariableDecl ( name , self . eat_variable_value ( ) ) ) ;
} else {
values . push ( Token {
kind : TokenKind ::Variable ( name ) ,
pos : tok . pos ,
} ) ;
}
}
2020-01-08 20:39:05 -05:00
TokenKind ::MultilineComment ( ref s ) = > {
self . devour_whitespace ( ) ;
if values . is_empty ( ) {
return Ok ( Expr ::MultilineComment ( s . clone ( ) ) ) ;
} else {
values . push ( tok . clone ( ) )
}
}
2020-01-04 22:55:04 -05:00
_ = > values . push ( tok . clone ( ) ) ,
} ;
}
Err ( ( ) )
}
fn devour_whitespace ( & mut self ) {
while let Some ( tok ) = self . lexer . peek ( ) {
match tok . kind {
TokenKind ::Whitespace ( _ ) = > {
self . lexer . next ( ) ;
}
_ = > break ,
}
}
}
}
2020-01-06 18:55:51 -05:00
fn main ( ) -> SassResult < ( ) > {
2020-01-04 22:55:04 -05:00
let mut stdout = std ::io ::stdout ( ) ;
2020-01-07 19:24:37 -05:00
let s = StyleSheet ::from_path ( " input.scss " ) ? ;
2020-01-04 22:55:04 -05:00
// dbg!(s);
2020-01-05 12:52:50 -05:00
// s.pretty_print(&mut stdout)?;
// s.pretty_print_selectors(&mut stdout)?;
s . print_as_css ( & mut stdout ) ? ;
// dbg!(Css::from_stylesheet(s));
2020-01-04 22:55:04 -05:00
// println!("{}", s);
// drop(input);
Ok ( ( ) )
}
2020-01-05 20:26:34 -05:00
#[ cfg(test) ]
mod test_css {
use super ::StyleSheet ;
macro_rules ! test {
( $func :ident , $input :literal ) = > {
#[ test ]
fn $func ( ) {
let mut buf = Vec ::new ( ) ;
2020-01-06 19:23:27 -05:00
StyleSheet ::new ( $input )
. expect ( concat! ( " failed to parse on " , $input ) )
2020-01-05 20:26:34 -05:00
. print_as_css ( & mut buf )
. expect ( concat! ( " failed to pretty print on " , $input ) ) ;
assert_eq! (
String ::from ( $input ) ,
String ::from_utf8 ( buf ) . expect ( " produced invalid utf8 " )
) ;
}
} ;
( $func :ident , $input :literal , $output :literal ) = > {
#[ test ]
fn $func ( ) {
let mut buf = Vec ::new ( ) ;
2020-01-06 19:23:27 -05:00
StyleSheet ::new ( $input )
. expect ( concat! ( " failed to parse on " , $input ) )
2020-01-05 20:26:34 -05:00
. print_as_css ( & mut buf )
. expect ( concat! ( " failed to pretty print on " , $input ) ) ;
assert_eq! (
String ::from ( $output ) ,
String ::from_utf8 ( buf ) . expect ( " produced invalid utf8 " )
) ;
}
} ;
}
2020-01-05 20:51:14 -05:00
test! (
2020-01-11 14:51:31 -05:00
selector_nesting_el_mul_el ,
2020-01-05 20:51:14 -05:00
" a, b { \n a, b { \n color: red \n } \n } \n " ,
2020-01-11 14:51:31 -05:00
" a a, a b, b a, b b { \n color: red; \n } \n "
) ;
test! ( selector_element , " a { \n color: red; \n } \n " ) ;
test! ( selector_id , " #id { \n color: red; \n } \n " ) ;
test! ( selector_class , " .class { \n color: red; \n } \n " ) ;
test! ( selector_el_descendant , " a a { \n color: red; \n } \n " ) ;
test! ( selector_universal , " * { \n color: red; \n } \n " ) ;
test! ( selector_el_class_and , " a.class { \n color: red; \n } \n " ) ;
test! ( selector_el_id_and , " a#class { \n color: red; \n } \n " ) ;
test! (
selector_el_class_descendant ,
" a .class { \n color: red; \n } \n "
) ;
test! ( selector_el_id_descendant , " a #class { \n color: red; \n } \n " ) ;
test! (
selector_el_universal_descendant ,
" a * { \n color: red; \n } \n "
) ;
test! (
selector_universal_el_descendant ,
" * a { \n color: red; \n } \n "
) ;
test! ( selector_attribute_any , " [attr] { \n color: red; \n } \n " ) ;
test! (
selector_attribute_equals ,
" [attr=val] { \n color: red; \n } \n "
) ;
test! (
selector_attribute_single_quotes ,
" [attr='val'] { \n color: red; \n } \n "
) ;
test! (
selector_attribute_double_quotes ,
" [attr= \" val \" ] { \n color: red; \n } \n "
2020-01-05 20:51:14 -05:00
) ;
2020-01-11 14:51:31 -05:00
test! ( selector_attribute_in , " [attr~=val] { \n color: red; \n } \n " ) ;
test! (
selector_attribute_begins_hyphen_or_exact ,
" [attr|=val] { \n color: red; \n } \n "
) ;
test! (
selector_attribute_starts_with ,
" [attr^=val] { \n color: red; \n } \n "
) ;
test! (
selector_attribute_ends_with ,
" [attr$=val] { \n color: red; \n } \n "
) ;
test! (
selector_attribute_contains ,
" [attr*=val] { \n color: red; \n } \n "
) ;
test! ( selector_el_attribute_and , " a[attr] { \n color: red; \n } \n " ) ;
test! (
selector_el_attribute_descendant ,
" a [attr] { \n color: red; \n } \n "
) ;
test! ( selector_el_mul_el , " a, b { \n color: red; \n } \n " ) ;
test! (
selector_el_immediate_child_el ,
" a > b { \n color: red; \n } \n "
) ;
test! ( selector_el_following_el , " a + b { \n color: red; \n } \n " ) ;
test! ( selector_el_preceding_el , " a ~ b { \n color: red; \n } \n " ) ;
test! ( selector_pseudo , " :pseudo { \n color: red; \n } \n " ) ;
test! ( selector_el_pseudo_and , " a:pseudo { \n color: red; \n } \n " ) ;
test! (
selector_el_pseudo_descendant ,
" a :pseudo { \n color: red; \n } \n "
) ;
test! (
selector_pseudo_el_descendant ,
" :pseudo a { \n color: red; \n } \n "
) ;
2020-01-11 19:16:59 -05:00
test! (
selector_pseudo_paren_comma ,
" :pseudo(a, b, c) { \n color: red; \n } \n "
) ;
test! (
selector_pseudo_paren_space ,
" :pseudo(a b c) { \n color: red; \n } \n "
) ;
test! (
selector_el_pseudo_paren_and ,
" a:pseudo(a, b, c) { \n color: red; \n } \n "
) ;
test! (
selector_el_pseudo_paren_descendant ,
" a :pseudo(a, b, c) { \n color: red; \n } \n "
) ;
test! (
selector_pseudo_paren_el_descendant ,
" :pseudo(a, b, c) a { \n color: red; \n } \n "
) ;
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 "
) ;
2020-01-11 14:51:31 -05:00
2020-01-05 20:26:34 -05:00
test! ( basic_style , " a { \n color: red; \n } \n " ) ;
test! ( two_styles , " a { \n color: red; \n color: blue; \n } \n " ) ;
2020-01-09 21:30:21 -05:00
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 } \n a c { \n color: white; \n } \n "
) ;
test! (
two_rulesets ,
" a { \n color: red; \n } \n c { \n color: white; \n } \n "
) ;
test! ( two_inner_outer_rulesets , " a { \n b { \n color: red; \n } \n c { \n color: white; \n } \n } \n a { \n b { \n color: red; \n } \n c { \n color: white; \n } \n } \n " , " a b { \n color: red; \n } \n a c { \n color: white; \n } \n a b { \n color: red; \n } \n a c { \n color: white; \n } \n " ) ;
2020-01-05 20:26:34 -05:00
test! ( selector_mul , " a, b { \n color: red; \n } \n " ) ;
2020-01-08 20:39:05 -05:00
test! (
removes_empty_outer_styles ,
" a { \n b { \n color: red; \n } \n " ,
" a b { \n color: red; \n } \n "
) ;
2020-01-07 19:58:38 -05:00
test! ( removes_empty_styles , " a {} \n " , " " ) ;
2020-01-11 17:24:50 -05:00
test! (
doesnt_eat_style_after_ruleset ,
" a { \n b { \n color: red; \n } \n color: blue; \n } \n " ,
" a { \n color: blue; \n } \n a b { \n color: red; \n } \n "
) ;
2020-01-08 20:39:05 -05:00
test! (
removes_inner_comments ,
" a { \n color: red/* hi */; \n } \n " ,
" a { \n color: red; \n } \n "
) ;
test! (
removes_inner_comments_whitespace ,
" a { \n color: red /* hi */; \n } \n " ,
" a { \n color: red; \n } \n "
) ;
test! (
preserves_outer_comments_before ,
" a { \n /* hi */ \n color: red; \n } \n "
) ;
test! (
preserves_outer_comments_after ,
" a { \n color: red; \n /* hi */ \n } \n "
) ;
test! (
preserves_outer_comments_two ,
" a { \n /* foo */ \n /* bar */ \n color: red; \n } \n "
) ;
test! (
2020-01-09 20:56:09 -05:00
preserves_toplevel_comment_before ,
2020-01-08 20:39:05 -05:00
" /* foo */ \n a { \n color: red; \n } \n "
) ;
2020-01-09 20:56:09 -05:00
test! (
preserves_toplevel_comment_after ,
" a { \n color: red; \n } \n /* foo */ \n "
) ;
2020-01-08 20:39:05 -05:00
test! (
removes_single_line_comment ,
" // a { color: red } \n a { \n height: 1 1px; \n } \n " ,
" a { \n height: 1 1px; \n } \n "
) ;
2020-01-11 16:12:23 -05:00
test! (
outer_ampersand ,
" a, b { \n & c { \n color: red; \n } \n } \n " ,
" a c, b c { \n color: red; \n } \n "
) ;
test! (
inner_ampersand ,
" a, b { \n a & c { \n color: red; \n } \n } \n " ,
" a a c, a b c { \n color: red; \n } \n "
) ;
test! (
ampersand_multiple_whitespace ,
" a , b { \n &c { \n color: red; \n } \n } \n " ,
" ac, bc { \n color: red; \n } \n "
) ;
test! (
ampersand_alone ,
" a, b { \n & { \n color: red; \n } \n } \n " ,
" a, b { \n color: red; \n } \n "
) ;
2020-01-11 18:43:42 -05:00
test! (
bem_dash_dash_selector ,
" a { \n &--b { \n color: red; \n } \n } \n " ,
" a--b { \n color: red; \n } \n "
) ;
test! (
bem_underscore_selector ,
" a { \n &__b { \n color: red; \n } \n } \n " ,
" a__b { \n color: red; \n } \n "
) ;
2020-01-05 20:51:14 -05:00
}