support three level extend loop
the last feature stopping us from semantic parity with `dart-sass` when compiling bootstrap. this was a difficult bug -- it essentially boiled down to the fact that we weren't applying extensions to _super_ selectors. i suspect that this has somehow broken another feature of `@extend`, but all of our unit tests, the sass spec, and bootstrap seem to be correct, so i am considering this implemented.
This commit is contained in:
parent
6de7b113cf
commit
0edb60e2b3
71
src/lib.rs
71
src/lib.rs
@ -112,7 +112,7 @@ use crate::{
|
|||||||
Parser,
|
Parser,
|
||||||
},
|
},
|
||||||
scope::{Scope, Scopes},
|
scope::{Scope, Scopes},
|
||||||
selector::{Extender, Selector},
|
selector::{ExtendedSelector, Extender, SelectorList},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
@ -267,30 +267,20 @@ fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box<Error> {
|
|||||||
Box::new(Error::from_loc(message, map.look_up_span(span), unicode))
|
Box::new(Error::from_loc(message, map.look_up_span(span), unicode))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile CSS from a path
|
fn from_string_with_file_name(input: String, file_name: &str, options: &Options) -> Result<String> {
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// fn main() -> Result<(), Box<grass::Error>> {
|
|
||||||
/// 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<String> {
|
|
||||||
let mut map = CodeMap::new();
|
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 empty_span = file.span.subspan(0, 0);
|
||||||
|
|
||||||
let stmts = Parser {
|
let stmts = Parser {
|
||||||
toks: &mut Lexer::new_from_file(&file),
|
toks: &mut Lexer::new_from_file(&file),
|
||||||
map: &mut map,
|
map: &mut map,
|
||||||
path: p.as_ref(),
|
path: file_name.as_ref(),
|
||||||
scopes: &mut Scopes::new(),
|
scopes: &mut Scopes::new(),
|
||||||
global_scope: &mut Scope::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,
|
span_before: empty_span,
|
||||||
content: &mut Vec::new(),
|
content: &mut Vec::new(),
|
||||||
flags: ContextFlags::empty(),
|
flags: ContextFlags::empty(),
|
||||||
@ -311,6 +301,22 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
|||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile CSS from a path
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// fn main() -> Result<(), Box<grass::Error>> {
|
||||||
|
/// 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<String> {
|
||||||
|
from_string_with_file_name(String::from_utf8(fs::read(p)?)?, p, options)
|
||||||
|
}
|
||||||
|
|
||||||
/// Compile CSS from a string
|
/// Compile CSS from a string
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -323,35 +329,8 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
|||||||
#[cfg_attr(feature = "profiling", inline(never))]
|
#[cfg_attr(feature = "profiling", inline(never))]
|
||||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||||
#[cfg(not(feature = "wasm"))]
|
#[cfg(not(feature = "wasm"))]
|
||||||
pub fn from_string(p: String, options: &Options) -> Result<String> {
|
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
||||||
let mut map = CodeMap::new();
|
from_string_with_file_name(input, "stdin", options)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
|
@ -71,7 +71,7 @@ pub(crate) struct Parser<'a> {
|
|||||||
pub global_scope: &'a mut Scope,
|
pub global_scope: &'a mut Scope,
|
||||||
pub scopes: &'a mut Scopes,
|
pub scopes: &'a mut Scopes,
|
||||||
pub content_scopes: &'a mut Scopes,
|
pub content_scopes: &'a mut Scopes,
|
||||||
pub super_selectors: &'a mut NeverEmptyVec<Selector>,
|
pub super_selectors: &'a mut NeverEmptyVec<ExtendedSelector>,
|
||||||
pub span_before: Span,
|
pub span_before: Span,
|
||||||
pub content: &'a mut Vec<Content>,
|
pub content: &'a mut Vec<Content>,
|
||||||
pub flags: ContextFlags,
|
pub flags: ContextFlags,
|
||||||
@ -106,6 +106,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
self.at_root = true;
|
self.at_root = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(stmts)
|
Ok(stmts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,14 +359,15 @@ impl<'a> Parser<'a> {
|
|||||||
.parse_selector(true, false, init)?
|
.parse_selector(true, false, init)?
|
||||||
.0
|
.0
|
||||||
.resolve_parent_selectors(
|
.resolve_parent_selectors(
|
||||||
self.super_selectors.last(),
|
&self.super_selectors.last().clone().into_selector(),
|
||||||
!at_root || self.at_root_has_selector,
|
!at_root || self.at_root_has_selector,
|
||||||
)?;
|
)?;
|
||||||
self.scopes.enter_new_scope();
|
self.scopes.enter_new_scope();
|
||||||
self.super_selectors.push(selector.clone());
|
|
||||||
|
|
||||||
let extended_selector = self.extender.add_selector(selector.0, None);
|
let extended_selector = self.extender.add_selector(selector.0, None);
|
||||||
|
|
||||||
|
self.super_selectors.push(extended_selector.clone());
|
||||||
|
|
||||||
let body = self.parse_stmt()?;
|
let body = self.parse_stmt()?;
|
||||||
self.scopes.exit_scope();
|
self.scopes.exit_scope();
|
||||||
self.super_selectors.pop();
|
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 {
|
body = vec![Stmt::RuleSet {
|
||||||
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
|
selector: self.super_selectors.last().clone(),
|
||||||
body,
|
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 {
|
body = vec![Stmt::RuleSet {
|
||||||
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
|
selector: self.super_selectors.last().clone(),
|
||||||
body,
|
body,
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@ -723,9 +737,16 @@ impl<'a> Parser<'a> {
|
|||||||
self.super_selectors.last().clone()
|
self.super_selectors.last().clone()
|
||||||
} else {
|
} else {
|
||||||
at_root_has_selector = true;
|
at_root_has_selector = true;
|
||||||
self.parse_selector(true, false, String::new())?.0
|
let selector = self
|
||||||
}
|
.parse_selector(true, false, String::new())?
|
||||||
.resolve_parent_selectors(self.super_selectors.last(), false)?;
|
.0
|
||||||
|
.resolve_parent_selectors(
|
||||||
|
&self.super_selectors.last().clone().into_selector(),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.extender.add_selector(selector.0, None)
|
||||||
|
};
|
||||||
|
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
|
|
||||||
@ -760,7 +781,7 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
.collect::<SassResult<Vec<Stmt>>>()?;
|
.collect::<SassResult<Vec<Stmt>>>()?;
|
||||||
let mut stmts = vec![Stmt::RuleSet {
|
let mut stmts = vec![Stmt::RuleSet {
|
||||||
selector: ExtendedSelector::new(at_rule_selector.0),
|
selector: at_rule_selector,
|
||||||
body: styles,
|
body: styles,
|
||||||
}];
|
}];
|
||||||
stmts.extend(raw_stmts);
|
stmts.extend(raw_stmts);
|
||||||
@ -825,7 +846,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.extender.add_extension(
|
self.extender.add_extension(
|
||||||
super_selector.clone().0,
|
super_selector.clone().into_selector().0,
|
||||||
compound.components.first().unwrap(),
|
compound.components.first().unwrap(),
|
||||||
&extend_rule,
|
&extend_rule,
|
||||||
&None,
|
&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 {
|
body = vec![Stmt::RuleSet {
|
||||||
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
|
selector: self.super_selectors.last().clone(),
|
||||||
body,
|
body,
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -839,7 +839,11 @@ impl<'a> Parser<'a> {
|
|||||||
.span(span)
|
.span(span)
|
||||||
} else {
|
} else {
|
||||||
IntermediateValue::Value(HigherIntermediateValue::Literal(
|
IntermediateValue::Value(HigherIntermediateValue::Literal(
|
||||||
self.super_selectors.last().clone().into_value(),
|
self.super_selectors
|
||||||
|
.last()
|
||||||
|
.clone()
|
||||||
|
.into_selector()
|
||||||
|
.into_value(),
|
||||||
))
|
))
|
||||||
.span(span)
|
.span(span)
|
||||||
}
|
}
|
||||||
|
@ -1239,7 +1239,6 @@ test!(
|
|||||||
".foo, .bar {\n a: b;\n}\n\n.bar, .foo {\n c: d;\n}\n"
|
".foo, .bar {\n a: b;\n}\n\n.bar, .foo {\n c: d;\n}\n"
|
||||||
);
|
);
|
||||||
test!(
|
test!(
|
||||||
#[ignore = "Rc<RefCell<Selector>>"]
|
|
||||||
three_level_extend_loop,
|
three_level_extend_loop,
|
||||||
".foo {a: b; @extend .bar}
|
".foo {a: b; @extend .bar}
|
||||||
.bar {c: d; @extend .baz}
|
.bar {c: d; @extend .baz}
|
||||||
@ -1413,8 +1412,7 @@ test!(
|
|||||||
#[ignore = "media queries are not yet parsed correctly"]
|
#[ignore = "media queries are not yet parsed correctly"]
|
||||||
extend_within_separate_nested_at_rules,
|
extend_within_separate_nested_at_rules,
|
||||||
"@media screen {@flooblehoof {.foo {a: b}}}
|
"@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"
|
"@media screen {\n @flooblehoof {\n .foo, .bar {\n a: b;\n }\n }\n}\n@media screen {\n @flooblehoof {}\n}\n"
|
||||||
);
|
);
|
||||||
test!(
|
test!(
|
||||||
@ -1771,7 +1769,7 @@ test!(
|
|||||||
// extending rules that contain their own extends needs special handling.
|
// extending rules that contain their own extends needs special handling.
|
||||||
.b {@extend .a}
|
.b {@extend .a}
|
||||||
.c {@extend .b}
|
.c {@extend .b}
|
||||||
.a {x: y}
|
.a {x: y}
|
||||||
",
|
",
|
||||||
".a, .b, .c {\n x: y;\n}\n"
|
".a, .b, .c {\n x: y;\n}\n"
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user