allow !optional in @extend

This commit is contained in:
Connor Skees 2020-07-24 20:23:54 -04:00
parent 5634681fa2
commit 67cbf9591a
3 changed files with 24 additions and 20 deletions

View File

@ -15,7 +15,10 @@ use crate::{
ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
},
style::Style,
utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
utils::{
peek_ident_no_interpolation, read_until_closing_curly_brace,
read_until_semicolon_or_closing_curly_brace,
},
value::Value,
Options, {Cow, Token},
};
@ -269,6 +272,7 @@ impl<'a> Parser<'a> {
self.at_root = false;
let selector = self
.parse_selector(!self.super_selectors.is_empty(), false, init)?
.0
.resolve_parent_selectors(
self.super_selectors.last(),
!at_root || self.at_root_has_selector,
@ -299,7 +303,7 @@ impl<'a> Parser<'a> {
allows_parent: bool,
from_fn: bool,
mut string: String,
) -> SassResult<Selector> {
) -> SassResult<(Selector, bool)> {
let mut span = if let Some(tok) = self.toks.peek() {
tok.pos()
} else {
@ -310,6 +314,8 @@ impl<'a> Parser<'a> {
let mut found_curly = false;
let mut optional = false;
// we resolve interpolation and strip comments
while let Some(tok) = self.toks.next() {
span = span.merge(tok.pos());
@ -333,6 +339,16 @@ impl<'a> Parser<'a> {
found_curly = true;
break;
}
'!' => {
if peek_ident_no_interpolation(self.toks, false, self.span_before)?.node
== "optional"
{
self.toks.truncate_iterator_to_cursor();
optional = true;
} else {
string.push('!');
}
}
c => string.push(c),
}
}
@ -368,7 +384,7 @@ impl<'a> Parser<'a> {
)
.parse()?;
Ok(Selector(selector))
Ok((Selector(selector), optional))
}
/// Eat and return the contents of a comment.
@ -637,7 +653,7 @@ impl<'a> Parser<'a> {
self.super_selectors.last().clone()
} else {
at_root_has_selector = true;
self.parse_selector(true, false, String::new())?
self.parse_selector(true, false, String::new())?.0
}
.resolve_parent_selectors(self.super_selectors.last(), false)?;
@ -692,7 +708,7 @@ impl<'a> Parser<'a> {
// if !self.in_style_rule && !self.in_mixin && !self.in_content_block {
// return Err(("@extend may only be used within style rules.", self.span_before).into());
// }
let value = Parser {
let (value, is_optional) = Parser {
toks: &mut read_until_semicolon_or_closing_curly_brace(self.toks)?
.into_iter()
.peekmore(),
@ -712,17 +728,6 @@ impl<'a> Parser<'a> {
}
.parse_selector(false, true, String::new())?;
let is_optional = if let Some(Token { kind: '!', .. }) = self.toks.peek() {
self.toks.next();
assert_eq!(
self.parse_identifier_no_interpolation(false)?.node,
"optional"
);
true
} else {
false
};
self.whitespace();
if let Some(Token { kind: ';', .. }) = self.toks.peek() {

View File

@ -457,7 +457,7 @@ impl Value {
Some(v) => v,
None => return Err((format!("${}: {} is not a valid selector: it must be a string, a list of strings, or a list of lists of strings.", name, self.inspect(parser.span_before)?), parser.span_before).into()),
};
Parser {
Ok(Parser {
toks: &mut string
.chars()
.map(|c| Token::new(parser.span_before, c))
@ -478,7 +478,8 @@ impl Value {
content_scopes: parser.content_scopes,
options: parser.options,
}
.parse_selector(allows_parent, true, String::new())
.parse_selector(allows_parent, true, String::new())?
.0)
}
fn selector_string(self, span: Span) -> SassResult<Option<String>> {

View File

@ -1428,13 +1428,11 @@ test!(
"a.bar {\n a: b;\n}\n\n.bar, b.foo {\n c: d;\n}\n"
);
test!(
#[ignore = "!optional extend is not yet implemented"]
optional_extend_succeeds_when_extendee_doesnt_exist,
".foo {@extend .bar !optional}",
""
);
test!(
#[ignore = "!optional extend is not yet implemented"]
optional_extend_succeeds_when_extension_fails,
"a.bar {a: b}
b.foo {@extend .bar !optional}