diff --git a/src/lib.rs b/src/lib.rs index 014ca14..101756e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,7 @@ use crate::{ Parser, }, scope::{Scope, Scopes}, - selector::{Extender, Selector}, + selector::{ExtendedSelector, Extender, SelectorList}, }; mod args; @@ -267,30 +267,20 @@ fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box { Box::new(Error::from_loc(message, map.look_up_span(span), unicode)) } -/// Compile CSS from a path -/// -/// ``` -/// fn main() -> Result<(), Box> { -/// let sass = grass::from_path("input.scss", &grass::Options::default())?; -/// Ok(()) -/// } -/// ``` -/// (grass does not currently allow files or paths that are not valid UTF-8) -#[cfg_attr(feature = "profiling", inline(never))] -#[cfg_attr(not(feature = "profiling"), inline)] -#[cfg(not(feature = "wasm"))] -pub fn from_path(p: &str, options: &Options) -> Result { +fn from_string_with_file_name(input: String, file_name: &str, options: &Options) -> Result { let mut map = CodeMap::new(); - let file = map.add_file(p.into(), String::from_utf8(fs::read(p)?)?); + let file = map.add_file(file_name.to_owned(), input); let empty_span = file.span.subspan(0, 0); let stmts = Parser { toks: &mut Lexer::new_from_file(&file), map: &mut map, - path: p.as_ref(), + path: file_name.as_ref(), scopes: &mut Scopes::new(), global_scope: &mut Scope::new(), - super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), + super_selectors: &mut NeverEmptyVec::new(ExtendedSelector::new(SelectorList::new( + empty_span, + ))), span_before: empty_span, content: &mut Vec::new(), flags: ContextFlags::empty(), @@ -311,6 +301,22 @@ pub fn from_path(p: &str, options: &Options) -> Result { .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages)) } +/// Compile CSS from a path +/// +/// ``` +/// fn main() -> Result<(), Box> { +/// let sass = grass::from_path("input.scss", &grass::Options::default())?; +/// Ok(()) +/// } +/// ``` +/// (grass does not currently allow files or paths that are not valid UTF-8) +#[cfg_attr(feature = "profiling", inline(never))] +#[cfg_attr(not(feature = "profiling"), inline)] +#[cfg(not(feature = "wasm"))] +pub fn from_path(p: &str, options: &Options) -> Result { + from_string_with_file_name(String::from_utf8(fs::read(p)?)?, p, options) +} + /// Compile CSS from a string /// /// ``` @@ -323,35 +329,8 @@ pub fn from_path(p: &str, options: &Options) -> Result { #[cfg_attr(feature = "profiling", inline(never))] #[cfg_attr(not(feature = "profiling"), inline)] #[cfg(not(feature = "wasm"))] -pub fn from_string(p: String, options: &Options) -> Result { - let mut map = CodeMap::new(); - let file = map.add_file("stdin".into(), p); - let empty_span = file.span.subspan(0, 0); - let stmts = Parser { - toks: &mut Lexer::new_from_file(&file), - map: &mut map, - path: Path::new(""), - scopes: &mut Scopes::new(), - global_scope: &mut Scope::new(), - super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), - span_before: empty_span, - content: &mut Vec::new(), - flags: ContextFlags::empty(), - at_root: true, - at_root_has_selector: false, - extender: &mut Extender::new(empty_span), - content_scopes: &mut Scopes::new(), - options, - modules: &mut Modules::default(), - module_config: &mut ModuleConfig::default(), - } - .parse() - .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; - - Css::from_stmts(stmts, false, options.allows_charset) - .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))? - .pretty_print(&map, options.style) - .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages)) +pub fn from_string(input: String, options: &Options) -> Result { + from_string_with_file_name(input, "stdin", options) } #[cfg(feature = "wasm")] diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 52227d7..7afc24d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -71,7 +71,7 @@ pub(crate) struct Parser<'a> { pub global_scope: &'a mut Scope, pub scopes: &'a mut Scopes, pub content_scopes: &'a mut Scopes, - pub super_selectors: &'a mut NeverEmptyVec, + pub super_selectors: &'a mut NeverEmptyVec, pub span_before: Span, pub content: &'a mut Vec, pub flags: ContextFlags, @@ -106,6 +106,7 @@ impl<'a> Parser<'a> { } self.at_root = true; } + Ok(stmts) } @@ -358,14 +359,15 @@ impl<'a> Parser<'a> { .parse_selector(true, false, init)? .0 .resolve_parent_selectors( - self.super_selectors.last(), + &self.super_selectors.last().clone().into_selector(), !at_root || self.at_root_has_selector, )?; self.scopes.enter_new_scope(); - self.super_selectors.push(selector.clone()); let extended_selector = self.extender.add_selector(selector.0, None); + self.super_selectors.push(extended_selector.clone()); + let body = self.parse_stmt()?; self.scopes.exit_scope(); self.super_selectors.pop(); @@ -660,9 +662,15 @@ impl<'a> Parser<'a> { } } - if !self.super_selectors.last().is_empty() { + if !self + .super_selectors + .last() + .clone() + .into_selector() + .is_empty() + { body = vec![Stmt::RuleSet { - selector: ExtendedSelector::new(self.super_selectors.last().clone().0), + selector: self.super_selectors.last().clone(), body, }]; } @@ -700,9 +708,15 @@ impl<'a> Parser<'a> { } } - if !self.super_selectors.last().is_empty() { + if !self + .super_selectors + .last() + .clone() + .into_selector() + .is_empty() + { body = vec![Stmt::RuleSet { - selector: ExtendedSelector::new(self.super_selectors.last().clone().0), + selector: self.super_selectors.last().clone(), body, }]; } @@ -723,9 +737,16 @@ impl<'a> Parser<'a> { self.super_selectors.last().clone() } else { at_root_has_selector = true; - self.parse_selector(true, false, String::new())?.0 - } - .resolve_parent_selectors(self.super_selectors.last(), false)?; + let selector = self + .parse_selector(true, false, String::new())? + .0 + .resolve_parent_selectors( + &self.super_selectors.last().clone().into_selector(), + false, + )?; + + self.extender.add_selector(selector.0, None) + }; self.whitespace(); @@ -760,7 +781,7 @@ impl<'a> Parser<'a> { }) .collect::>>()?; let mut stmts = vec![Stmt::RuleSet { - selector: ExtendedSelector::new(at_rule_selector.0), + selector: at_rule_selector, body: styles, }]; stmts.extend(raw_stmts); @@ -825,7 +846,7 @@ impl<'a> Parser<'a> { } self.extender.add_extension( - super_selector.clone().0, + super_selector.clone().into_selector().0, compound.components.first().unwrap(), &extend_rule, &None, @@ -859,9 +880,15 @@ impl<'a> Parser<'a> { } } - if !self.super_selectors.last().is_empty() { + if !self + .super_selectors + .last() + .clone() + .into_selector() + .is_empty() + { body = vec![Stmt::RuleSet { - selector: ExtendedSelector::new(self.super_selectors.last().clone().0), + selector: self.super_selectors.last().clone(), body, }]; } diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index a2e0548..d60bdc3 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -839,7 +839,11 @@ impl<'a> Parser<'a> { .span(span) } else { IntermediateValue::Value(HigherIntermediateValue::Literal( - self.super_selectors.last().clone().into_value(), + self.super_selectors + .last() + .clone() + .into_selector() + .into_value(), )) .span(span) } diff --git a/tests/extend.rs b/tests/extend.rs index 917965a..37eea73 100644 --- a/tests/extend.rs +++ b/tests/extend.rs @@ -1239,7 +1239,6 @@ test!( ".foo, .bar {\n a: b;\n}\n\n.bar, .foo {\n c: d;\n}\n" ); test!( - #[ignore = "Rc>"] three_level_extend_loop, ".foo {a: b; @extend .bar} .bar {c: d; @extend .baz} @@ -1413,8 +1412,7 @@ 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 {@flooblehoof {.bar {@extend .foo}}}", "@media screen {\n @flooblehoof {\n .foo, .bar {\n a: b;\n }\n }\n}\n@media screen {\n @flooblehoof {}\n}\n" ); test!( @@ -1771,7 +1769,7 @@ test!( // extending rules that contain their own extends needs special handling. .b {@extend .a} .c {@extend .b} - .a {x: y} + .a {x: y} ", ".a, .b, .c {\n x: y;\n}\n" );