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, ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
}, },
style::Style, 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, value::Value,
Options, {Cow, Token}, Options, {Cow, Token},
}; };
@ -269,6 +272,7 @@ impl<'a> Parser<'a> {
self.at_root = false; self.at_root = false;
let selector = self let selector = self
.parse_selector(!self.super_selectors.is_empty(), false, init)? .parse_selector(!self.super_selectors.is_empty(), false, init)?
.0
.resolve_parent_selectors( .resolve_parent_selectors(
self.super_selectors.last(), self.super_selectors.last(),
!at_root || self.at_root_has_selector, !at_root || self.at_root_has_selector,
@ -299,7 +303,7 @@ impl<'a> Parser<'a> {
allows_parent: bool, allows_parent: bool,
from_fn: bool, from_fn: bool,
mut string: String, mut string: String,
) -> SassResult<Selector> { ) -> SassResult<(Selector, bool)> {
let mut span = if let Some(tok) = self.toks.peek() { let mut span = if let Some(tok) = self.toks.peek() {
tok.pos() tok.pos()
} else { } else {
@ -310,6 +314,8 @@ impl<'a> Parser<'a> {
let mut found_curly = false; let mut found_curly = false;
let mut optional = false;
// we resolve interpolation and strip comments // we resolve interpolation and strip comments
while let Some(tok) = self.toks.next() { while let Some(tok) = self.toks.next() {
span = span.merge(tok.pos()); span = span.merge(tok.pos());
@ -333,6 +339,16 @@ impl<'a> Parser<'a> {
found_curly = true; found_curly = true;
break; 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), c => string.push(c),
} }
} }
@ -368,7 +384,7 @@ impl<'a> Parser<'a> {
) )
.parse()?; .parse()?;
Ok(Selector(selector)) Ok((Selector(selector), optional))
} }
/// Eat and return the contents of a comment. /// Eat and return the contents of a comment.
@ -637,7 +653,7 @@ 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())? self.parse_selector(true, false, String::new())?.0
} }
.resolve_parent_selectors(self.super_selectors.last(), false)?; .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 { // 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()); // 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)? toks: &mut read_until_semicolon_or_closing_curly_brace(self.toks)?
.into_iter() .into_iter()
.peekmore(), .peekmore(),
@ -712,17 +728,6 @@ impl<'a> Parser<'a> {
} }
.parse_selector(false, true, String::new())?; .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(); self.whitespace();
if let Some(Token { kind: ';', .. }) = self.toks.peek() { if let Some(Token { kind: ';', .. }) = self.toks.peek() {

View File

@ -457,7 +457,7 @@ impl Value {
Some(v) => v, 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()), 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 toks: &mut string
.chars() .chars()
.map(|c| Token::new(parser.span_before, c)) .map(|c| Token::new(parser.span_before, c))
@ -478,7 +478,8 @@ impl Value {
content_scopes: parser.content_scopes, content_scopes: parser.content_scopes,
options: parser.options, 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>> { 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" "a.bar {\n a: b;\n}\n\n.bar, b.foo {\n c: d;\n}\n"
); );
test!( test!(
#[ignore = "!optional extend is not yet implemented"]
optional_extend_succeeds_when_extendee_doesnt_exist, optional_extend_succeeds_when_extendee_doesnt_exist,
".foo {@extend .bar !optional}", ".foo {@extend .bar !optional}",
"" ""
); );
test!( test!(
#[ignore = "!optional extend is not yet implemented"]
optional_extend_succeeds_when_extension_fails, optional_extend_succeeds_when_extension_fails,
"a.bar {a: b} "a.bar {a: b}
b.foo {@extend .bar !optional} b.foo {@extend .bar !optional}