grass/src/parse/mod.rs

850 lines
30 KiB
Rust
Raw Normal View History

2020-06-16 22:34:01 -04:00
use std::{convert::TryFrom, path::Path, vec::IntoIter};
2020-06-16 19:38:30 -04:00
use codemap::{CodeMap, Span, Spanned};
use peekmore::{PeekMore, PeekMoreIterator};
2020-06-16 20:00:11 -04:00
use crate::{
2020-07-04 20:50:53 -04:00
atrule::{
keyframes::{Keyframes, KeyframesRuleSet},
media::MediaRule,
AtRuleKind, Content, SupportsRule, UnknownAtRule,
},
2020-06-16 20:00:11 -04:00
error::SassResult,
2020-07-08 14:51:04 -04:00
scope::{Scope, Scopes},
selector::{
ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
},
2020-06-16 20:00:11 -04:00
style::Style,
2020-07-24 20:23:54 -04:00
utils::{
peek_ident_no_interpolation, read_until_closing_curly_brace,
read_until_semicolon_or_closing_curly_brace,
},
value::Value,
Options, {Cow, Token},
2020-06-16 19:38:30 -04:00
};
2020-07-07 19:37:48 -04:00
use common::{Comment, ContextFlags, NeverEmptyVec, SelectorOrStyle};
2020-07-03 12:38:20 -04:00
pub(crate) use value::{HigherIntermediateValue, ValueVisitor};
2020-06-16 19:38:30 -04:00
mod args;
pub mod common;
mod control_flow;
2020-06-16 22:34:01 -04:00
mod function;
2020-06-16 19:38:30 -04:00
mod ident;
2020-06-16 22:34:01 -04:00
mod import;
2020-07-04 20:50:53 -04:00
mod keyframes;
2020-06-24 11:39:32 -04:00
mod media;
2020-06-16 22:34:01 -04:00
mod mixin;
mod style;
2020-07-07 19:36:13 -04:00
mod throw_away;
2020-06-16 19:38:30 -04:00
mod value;
2020-06-16 22:34:01 -04:00
mod variable;
2020-06-16 19:38:30 -04:00
#[derive(Debug, Clone)]
pub(crate) enum Stmt {
RuleSet {
selector: ExtendedSelector,
2020-06-16 19:38:30 -04:00
body: Vec<Self>,
},
2020-06-25 00:27:24 -04:00
Style(Style),
Media(Box<MediaRule>),
UnknownAtRule(Box<UnknownAtRule>),
Supports(Box<SupportsRule>),
2020-06-16 19:38:30 -04:00
AtRoot {
body: Vec<Stmt>,
},
Comment(String),
2020-06-25 00:27:24 -04:00
Return(Box<Value>),
2020-07-04 20:50:53 -04:00
Keyframes(Box<Keyframes>),
KeyframesRuleSet(Box<KeyframesRuleSet>),
/// A plain import such as `@import "foo.css";` or
/// `@import url(https://fonts.google.com/foo?bar);`
Import(String),
2020-06-16 19:38:30 -04:00
}
/// We could use a generic for the toks, but it makes the API
/// much simpler to work with if it isn't generic. The performance
/// hit (if there is one) is not important for now.
2020-07-05 19:16:44 +08:00
// todo: merge at_root and at_root_has_selector into an enum
2020-06-16 19:38:30 -04:00
pub(crate) struct Parser<'a> {
pub toks: &'a mut PeekMoreIterator<IntoIter<Token>>,
pub map: &'a mut CodeMap,
pub path: &'a Path,
pub global_scope: &'a mut Scope,
2020-07-08 14:51:04 -04:00
pub scopes: &'a mut Scopes,
2020-07-08 17:52:37 -04:00
pub content_scopes: &'a mut Scopes,
2020-06-16 19:38:30 -04:00
pub super_selectors: &'a mut NeverEmptyVec<Selector>,
pub span_before: Span,
2020-07-02 15:22:15 -04:00
pub content: &'a mut Vec<Content>,
2020-07-05 10:13:49 -04:00
pub flags: ContextFlags,
2020-06-16 19:38:30 -04:00
/// Whether this parser is at the root of the document
/// E.g. not inside a style, mixin, or function
pub at_root: bool,
/// If this parser is inside an `@at-rule` block, this is whether or
/// not the `@at-rule` block has a super selector
pub at_root_has_selector: bool,
2020-06-18 16:56:03 -04:00
pub extender: &'a mut Extender,
2020-07-13 14:52:52 +01:00
pub options: &'a Options<'a>,
2020-06-16 19:38:30 -04:00
}
impl<'a> Parser<'a> {
pub fn parse(&mut self) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new();
while self.toks.peek().is_some() {
stmts.append(&mut self.parse_stmt()?);
2020-07-05 10:13:49 -04:00
if self.flags.in_function() && !stmts.is_empty() {
2020-06-16 19:38:30 -04:00
return Ok(stmts);
}
self.at_root = true;
}
Ok(stmts)
}
fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new();
while let Some(Token { kind, pos }) = self.toks.peek() {
2020-07-05 10:13:49 -04:00
if self.flags.in_function() && !stmts.is_empty() {
2020-06-16 19:38:30 -04:00
return Ok(stmts);
}
2020-06-17 02:28:35 -04:00
self.span_before = *pos;
2020-06-16 19:38:30 -04:00
match kind {
'@' => {
self.toks.next();
let kind_string = self.parse_identifier()?;
2020-06-17 02:28:35 -04:00
self.span_before = kind_string.span;
2020-06-16 19:38:30 -04:00
match AtRuleKind::try_from(&kind_string)? {
AtRuleKind::Import => stmts.append(&mut self.import()?),
AtRuleKind::Mixin => self.parse_mixin()?,
2020-07-02 10:31:32 -04:00
AtRuleKind::Content => stmts.append(&mut self.parse_content_rule()?),
2020-06-16 19:38:30 -04:00
AtRuleKind::Include => stmts.append(&mut self.parse_include()?),
AtRuleKind::Function => self.parse_function()?,
AtRuleKind::Return => {
2020-07-05 10:13:49 -04:00
if self.flags.in_function() {
2020-06-16 19:38:30 -04:00
return Ok(vec![Stmt::Return(self.parse_return()?)]);
} else {
return Err((
"This at-rule is not allowed here.",
kind_string.span,
)
.into());
}
}
AtRuleKind::AtRoot => {
if self.at_root {
stmts.append(&mut self.parse_at_root()?);
} else {
stmts.push(Stmt::AtRoot {
body: self.parse_at_root()?,
});
}
}
AtRuleKind::Error => {
let Spanned {
node: message,
span,
} = self.parse_value(false)?;
2020-06-16 19:38:30 -04:00
return Err((
message.inspect(span)?.to_string(),
span.merge(kind_string.span),
)
.into());
}
AtRuleKind::Warn => {
let Spanned {
node: message,
span,
} = self.parse_value(false)?;
2020-06-16 19:38:30 -04:00
span.merge(kind_string.span);
2020-06-18 03:09:24 -04:00
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
kind_string.span.merge(*pos);
self.toks.next();
2020-06-16 19:38:30 -04:00
}
self.warn(&Spanned {
node: message.to_css_string(span)?,
span,
})
}
AtRuleKind::Debug => {
let Spanned {
node: message,
span,
} = self.parse_value(false)?;
2020-06-16 19:38:30 -04:00
span.merge(kind_string.span);
2020-06-18 03:09:24 -04:00
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
kind_string.span.merge(*pos);
self.toks.next();
2020-06-16 19:38:30 -04:00
}
self.debug(&Spanned {
node: message.inspect(span)?,
span,
})
}
AtRuleKind::If => stmts.append(&mut self.parse_if()?),
AtRuleKind::Each => stmts.append(&mut self.parse_each()?),
AtRuleKind::For => stmts.append(&mut self.parse_for()?),
AtRuleKind::While => stmts.append(&mut self.parse_while()?),
AtRuleKind::Charset => {
read_until_semicolon_or_closing_curly_brace(self.toks)?;
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
self.toks.next();
}
continue;
}
AtRuleKind::Media => stmts.push(self.parse_media()?),
2020-07-04 20:50:53 -04:00
AtRuleKind::Unknown(_) => {
2020-06-16 19:38:30 -04:00
stmts.push(self.parse_unknown_at_rule(kind_string.node)?)
}
AtRuleKind::Use => todo!("@use not yet implemented"),
AtRuleKind::Forward => todo!("@forward not yet implemented"),
AtRuleKind::Extend => self.parse_extend()?,
AtRuleKind::Supports => stmts.push(self.parse_supports()?),
2020-07-04 20:50:53 -04:00
AtRuleKind::Keyframes => stmts.push(self.parse_keyframes()?),
2020-06-16 19:38:30 -04:00
}
}
'$' => self.parse_variable_declaration()?,
'\t' | '\n' | ' ' | ';' => {
self.toks.next();
continue;
}
'/' => {
self.toks.next();
let comment = self.parse_comment()?;
self.whitespace();
2020-06-16 19:38:30 -04:00
match comment.node {
Comment::Silent => continue,
Comment::Loud(s) => stmts.push(Stmt::Comment(s)),
}
}
'\u{0}'..='\u{8}' | '\u{b}'..='\u{1f}' => {
2020-06-17 02:28:35 -04:00
return Err(("expected selector.", *pos).into())
2020-06-16 19:38:30 -04:00
}
'}' => {
self.toks.next();
break;
}
// dart-sass seems to special-case the error message here?
'!' | '{' => return Err(("expected \"}\".", *pos).into()),
2020-07-04 20:50:53 -04:00
_ => {
2020-07-05 10:13:49 -04:00
if self.flags.in_keyframes() {
2020-07-04 20:50:53 -04:00
match self.is_selector_or_style()? {
SelectorOrStyle::Style(property, value) => {
if let Some(value) = value {
stmts.push(Stmt::Style(Style { property, value }));
} else {
stmts.extend(
self.parse_style_group(property)?
.into_iter()
.map(Stmt::Style),
);
}
}
SelectorOrStyle::Selector(init) => {
let selector = self.parse_keyframes_selector(init)?;
2020-07-08 14:51:04 -04:00
self.scopes.enter_new_scope();
2020-07-04 20:50:53 -04:00
let body = self.parse_stmt()?;
2020-07-08 14:51:04 -04:00
self.scopes.exit_scope();
2020-07-04 20:50:53 -04:00
stmts.push(Stmt::KeyframesRuleSet(Box::new(KeyframesRuleSet {
selector,
body,
})));
}
2020-06-27 07:24:53 -04:00
}
2020-07-04 20:50:53 -04:00
continue;
2020-06-16 19:38:30 -04:00
}
2020-07-04 20:50:53 -04:00
match self.is_selector_or_style()? {
SelectorOrStyle::Style(property, value) => {
if let Some(value) = value {
stmts.push(Stmt::Style(Style { property, value }));
} else {
stmts.extend(
self.parse_style_group(property)?
.into_iter()
.map(Stmt::Style),
);
}
}
SelectorOrStyle::Selector(init) => {
let at_root = self.at_root;
self.at_root = false;
let selector = self
.parse_selector(!self.super_selectors.is_empty(), false, init)?
2020-07-24 20:23:54 -04:00
.0
2020-07-04 20:50:53 -04:00
.resolve_parent_selectors(
self.super_selectors.last(),
!at_root || self.at_root_has_selector,
)?;
2020-07-08 14:51:04 -04:00
self.scopes.enter_new_scope();
2020-07-04 20:50:53 -04:00
self.super_selectors.push(selector.clone());
let extended_selector = self.extender.add_selector(selector.0, None);
let body = self.parse_stmt()?;
2020-07-08 14:51:04 -04:00
self.scopes.exit_scope();
2020-07-04 20:50:53 -04:00
self.super_selectors.pop();
self.at_root = self.super_selectors.is_empty();
stmts.push(Stmt::RuleSet {
selector: extended_selector,
body,
});
}
2020-06-16 19:38:30 -04:00
}
2020-07-04 20:50:53 -04:00
}
2020-06-16 19:38:30 -04:00
}
}
Ok(stmts)
}
pub fn parse_selector(
&mut self,
allows_parent: bool,
from_fn: bool,
mut string: String,
2020-07-24 20:23:54 -04:00
) -> SassResult<(Selector, bool)> {
2020-06-16 19:38:30 -04:00
let mut span = if let Some(tok) = self.toks.peek() {
tok.pos()
} else {
return Err(("expected \"{\".", self.span_before).into());
};
2020-06-17 02:28:35 -04:00
self.span_before = span;
2020-06-16 19:38:30 -04:00
let mut found_curly = false;
2020-07-24 20:23:54 -04:00
let mut optional = false;
// we resolve interpolation and strip comments
2020-06-16 19:38:30 -04:00
while let Some(tok) = self.toks.next() {
span = span.merge(tok.pos());
match tok.kind {
'#' => {
if let Some(Token { kind: '{', .. }) = self.toks.peek().cloned() {
self.toks.next();
string.push_str(&self.parse_interpolation()?.to_css_string(span)?);
} else {
string.push('#');
}
}
'/' => {
if self.toks.peek().is_none() {
return Err(("Expected selector.", tok.pos()).into());
}
self.parse_comment()?;
string.push(' ');
}
'{' => {
found_curly = true;
break;
}
2020-07-24 20:23:54 -04:00
'!' => {
if peek_ident_no_interpolation(self.toks, false, self.span_before)?.node
== "optional"
{
self.toks.truncate_iterator_to_cursor();
optional = true;
} else {
string.push('!');
}
}
2020-06-16 19:38:30 -04:00
c => string.push(c),
}
}
if !found_curly && !from_fn {
return Err(("expected \"{\".", span).into());
}
let sel_toks: Vec<Token> = string.chars().map(|x| Token::new(span, x)).collect();
let mut iter = sel_toks.into_iter().peekmore();
2020-06-18 16:56:03 -04:00
let selector = SelectorParser::new(
&mut Parser {
toks: &mut iter,
map: self.map,
path: self.path,
scopes: self.scopes,
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
2020-06-26 01:02:06 -04:00
content: self.content,
2020-07-05 19:16:44 +08:00
flags: self.flags,
2020-06-18 16:56:03 -04:00
at_root: self.at_root,
at_root_has_selector: self.at_root_has_selector,
extender: self.extender,
2020-07-08 17:52:37 -04:00
content_scopes: self.content_scopes,
options: self.options,
2020-06-18 16:56:03 -04:00
},
allows_parent,
true,
span,
)
.parse()?;
2020-07-24 20:23:54 -04:00
Ok((Selector(selector), optional))
2020-06-16 19:38:30 -04:00
}
/// Eat and return the contents of a comment.
///
/// This function assumes that the starting "/" has already been consumed
/// The entirety of the comment, including the ending "*/" for multiline comments,
/// is consumed. Note that the ending "*/" is not included in the output.
#[allow(clippy::eval_order_dependence)]
pub fn parse_comment(&mut self) -> SassResult<Spanned<Comment>> {
let mut span = self.span_before;
Ok(Spanned {
node: match self.toks.next() {
Some(Token { kind: '/', .. }) => {
while let Some(tok) = self.toks.peek() {
2020-06-16 19:38:30 -04:00
if tok.kind == '\n' {
break;
}
span = span.merge(tok.pos);
self.toks.next();
2020-06-16 19:38:30 -04:00
}
Comment::Silent
}
Some(Token { kind: '*', .. }) => {
let mut comment = String::new();
while let Some(tok) = self.toks.next() {
span = span.merge(tok.pos());
match (tok.kind, self.toks.peek()) {
('*', Some(Token { kind: '/', .. })) => {
self.toks.next();
break;
}
('#', Some(Token { kind: '{', .. })) => {
self.toks.next();
comment.push_str(&self.parse_interpolation()?.to_css_string(span)?);
continue;
}
(..) => comment.push(tok.kind),
}
}
Comment::Loud(comment)
}
Some(..) | None => return Err(("expected selector.", self.span_before).into()),
},
span,
})
}
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
let val = self.parse_value(true)?;
2020-06-16 19:38:30 -04:00
match self.toks.next() {
Some(Token { kind: '}', .. }) => {}
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
}
2020-07-03 12:38:20 -04:00
Ok(val.map_node(Value::unquote))
2020-06-16 19:38:30 -04:00
}
2020-06-24 11:39:32 -04:00
pub fn parse_interpolation_as_string(&mut self) -> SassResult<Cow<'static, str>> {
let interpolation = self.parse_interpolation()?;
Ok(match interpolation.node {
Value::String(v, ..) => Cow::owned(v),
v => v.to_css_string(interpolation.span)?,
})
}
pub fn parse_value_as_string_from_vec(
&mut self,
toks: Vec<Token>,
2020-07-09 13:43:27 -04:00
quoted: bool,
2020-06-24 11:39:32 -04:00
) -> SassResult<Cow<'static, str>> {
let value = self.parse_value_from_vec(toks, false)?;
2020-07-09 13:43:27 -04:00
if quoted {
value.node.to_css_string(value.span)
} else {
value.node.unquote().to_css_string(value.span)
}
2020-06-24 11:39:32 -04:00
}
2020-06-16 19:38:30 -04:00
pub fn whitespace(&mut self) -> bool {
let mut found_whitespace = false;
while let Some(tok) = self.toks.peek() {
match tok.kind {
' ' | '\t' | '\n' => {
self.toks.next();
found_whitespace = true;
}
_ => return found_whitespace,
}
}
found_whitespace
}
/// Eat tokens until a newline
///
/// This exists largely to eat silent comments, "//"
/// We only have to check for \n as the lexing step normalizes all newline characters
///
/// The newline is consumed
pub fn read_until_newline(&mut self) {
2020-06-16 19:38:30 -04:00
while let Some(tok) = self.toks.next() {
if tok.kind == '\n' {
break;
}
}
}
fn whitespace_or_comment(&mut self) -> bool {
let mut found_whitespace = false;
while let Some(tok) = self.toks.peek() {
match tok.kind {
' ' | '\t' | '\n' => {
self.toks.next();
found_whitespace = true;
}
'/' => match self.toks.peek_forward(1) {
Some(Token { kind: '*', .. }) => {
found_whitespace = true;
self.toks.next();
self.toks.next();
while let Some(tok) = self.toks.next() {
if tok.kind == '*' {
if let Some(Token { kind: '/', .. }) = self.toks.next() {
break;
}
}
}
}
Some(Token { kind: '/', .. }) => {
found_whitespace = true;
self.read_until_newline();
}
_ => {
2020-06-19 22:47:06 -04:00
self.toks.reset_cursor();
2020-06-16 19:38:30 -04:00
return found_whitespace;
}
},
_ => return found_whitespace,
}
}
found_whitespace
}
}
impl<'a> Parser<'a> {
fn parse_unknown_at_rule(&mut self, name: String) -> SassResult<Stmt> {
let mut params = String::new();
self.whitespace();
if let Some(Token { kind: ';', .. }) | None = self.toks.peek() {
self.toks.next();
2020-06-25 00:27:24 -04:00
return Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule {
2020-06-16 19:38:30 -04:00
name,
2020-06-22 12:39:09 -04:00
super_selector: Selector::new(self.span_before),
2020-06-16 19:38:30 -04:00
params: String::new(),
body: Vec::new(),
2020-06-25 00:27:24 -04:00
})));
2020-06-16 19:38:30 -04:00
}
while let Some(tok) = self.toks.next() {
match tok.kind {
'{' => break,
'#' => {
if let Some(Token { kind: '{', pos }) = self.toks.peek() {
self.span_before = self.span_before.merge(*pos);
self.toks.next();
2020-06-24 11:39:32 -04:00
params.push_str(&self.parse_interpolation_as_string()?);
2020-06-16 19:38:30 -04:00
} else {
params.push(tok.kind);
}
continue;
}
'\n' | ' ' | '\t' => {
self.whitespace();
params.push(' ');
continue;
}
_ => {}
}
params.push(tok.kind);
}
let raw_body = self.parse_stmt()?;
2020-06-16 19:38:30 -04:00
let mut rules = Vec::with_capacity(raw_body.len());
let mut body = Vec::new();
for stmt in raw_body {
match stmt {
Stmt::Style(..) => body.push(stmt),
_ => rules.push(stmt),
}
}
if !self.super_selectors.last().is_empty() {
body = vec![Stmt::RuleSet {
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
2020-06-16 19:38:30 -04:00
body,
}];
}
body.append(&mut rules);
2020-06-25 00:27:24 -04:00
Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule {
2020-06-16 19:38:30 -04:00
name,
2020-06-22 12:39:09 -04:00
super_selector: Selector::new(self.span_before),
2020-06-16 19:38:30 -04:00
params: params.trim().to_owned(),
body,
2020-06-25 00:27:24 -04:00
})))
2020-06-16 19:38:30 -04:00
}
fn parse_media(&mut self) -> SassResult<Stmt> {
2020-06-24 11:39:32 -04:00
let query = self.parse_media_query_list()?;
self.whitespace();
2020-06-16 19:38:30 -04:00
2020-06-24 11:39:32 -04:00
if !matches!(self.toks.next(), Some(Token { kind: '{', .. })) {
return Err(("expected \"{\".", self.span_before).into());
2020-06-16 19:38:30 -04:00
}
2020-07-25 18:33:59 -04:00
let raw_body = self.parse_stmt()?;
2020-06-16 19:38:30 -04:00
let mut rules = Vec::with_capacity(raw_body.len());
let mut body = Vec::new();
for stmt in raw_body {
match stmt {
Stmt::Style(..) => body.push(stmt),
_ => rules.push(stmt),
}
}
if !self.super_selectors.last().is_empty() {
body = vec![Stmt::RuleSet {
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
2020-06-16 19:38:30 -04:00
body,
}];
}
body.append(&mut rules);
2020-06-25 00:27:24 -04:00
Ok(Stmt::Media(Box::new(MediaRule {
2020-06-22 12:39:09 -04:00
super_selector: Selector::new(self.span_before),
2020-06-24 11:39:32 -04:00
query,
2020-06-16 19:38:30 -04:00
body,
2020-06-25 00:27:24 -04:00
})))
2020-06-16 19:38:30 -04:00
}
fn parse_at_root(&mut self) -> SassResult<Vec<Stmt>> {
self.whitespace();
let mut at_root_has_selector = false;
let at_rule_selector = if matches!(self.toks.peek(), Some(Token { kind: '{', .. })) {
self.toks.next();
self.super_selectors.last().clone()
} else {
at_root_has_selector = true;
2020-07-24 20:23:54 -04:00
self.parse_selector(true, false, String::new())?.0
2020-06-16 19:38:30 -04:00
}
2020-06-22 12:39:09 -04:00
.resolve_parent_selectors(self.super_selectors.last(), false)?;
2020-06-16 19:38:30 -04:00
self.whitespace();
let mut body = read_until_closing_curly_brace(self.toks)?;
2020-06-18 03:09:24 -04:00
body.push(match self.toks.next() {
Some(tok) => tok,
None => return Err(("expected \"}\".", self.span_before).into()),
});
2020-06-16 19:38:30 -04:00
self.whitespace();
let mut styles = Vec::new();
#[allow(clippy::unnecessary_filter_map)]
let raw_stmts = Parser {
toks: &mut body.into_iter().peekmore(),
map: self.map,
path: self.path,
scopes: self.scopes,
global_scope: self.global_scope,
super_selectors: &mut NeverEmptyVec::new(at_rule_selector.clone()),
span_before: self.span_before,
2020-06-26 01:02:06 -04:00
content: self.content,
2020-07-05 19:16:44 +08:00
flags: self.flags,
2020-06-16 19:38:30 -04:00
at_root: true,
at_root_has_selector,
2020-06-18 16:56:03 -04:00
extender: self.extender,
2020-07-08 17:52:37 -04:00
content_scopes: self.content_scopes,
options: self.options,
2020-06-16 19:38:30 -04:00
}
.parse()?
.into_iter()
.filter_map(|s| match s {
Stmt::Style(..) => {
styles.push(s);
None
}
2020-06-22 12:39:09 -04:00
_ => Some(Ok(s)),
2020-06-16 19:38:30 -04:00
})
2020-06-22 12:39:09 -04:00
.collect::<SassResult<Vec<Stmt>>>()?;
2020-06-16 19:38:30 -04:00
let mut stmts = vec![Stmt::RuleSet {
selector: ExtendedSelector::new(at_rule_selector.0),
2020-06-16 19:38:30 -04:00
body: styles,
}];
stmts.extend(raw_stmts);
Ok(stmts)
}
fn parse_extend(&mut self) -> SassResult<()> {
2020-06-18 16:56:03 -04:00
// todo: track when inside ruleset or `@content`
// 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());
// }
2020-07-24 20:23:54 -04:00
let (value, is_optional) = Parser {
2020-06-18 16:56:03 -04:00
toks: &mut read_until_semicolon_or_closing_curly_brace(self.toks)?
.into_iter()
.peekmore(),
map: self.map,
path: self.path,
scopes: self.scopes,
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
2020-06-26 01:02:06 -04:00
content: self.content,
2020-07-05 19:16:44 +08:00
flags: self.flags,
2020-06-18 16:56:03 -04:00
at_root: self.at_root,
at_root_has_selector: self.at_root_has_selector,
extender: self.extender,
2020-07-08 17:52:37 -04:00
content_scopes: self.content_scopes,
options: self.options,
2020-06-18 16:56:03 -04:00
}
.parse_selector(false, true, String::new())?;
self.whitespace();
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
self.toks.next();
}
let extend_rule = ExtendRule::new(value.clone(), is_optional, self.span_before);
let super_selector = self.super_selectors.last();
2020-06-18 16:56:03 -04:00
for complex in value.0.components {
if complex.components.len() != 1 || !complex.components.first().unwrap().is_compound() {
// If the selector was a compound selector but not a simple
// selector, emit a more explicit error.
return Err(("complex selectors may not be extended.", self.span_before).into());
}
let compound = match complex.components.first() {
Some(ComplexSelectorComponent::Compound(c)) => c.clone(),
Some(..) | None => todo!(),
};
if compound.components.len() != 1 {
return Err((
format!(
"compound selectors may no longer be extended.\nConsider `@extend {}` instead.\nSee http://bit.ly/ExtendCompound for details.\n",
compound.components.into_iter().map(|x| x.to_string()).collect::<Vec<String>>().join(", ")
)
, self.span_before).into());
}
self.extender.add_extension(
super_selector.clone().0,
2020-06-18 16:56:03 -04:00
compound.components.first().unwrap(),
&extend_rule,
&None,
self.span_before,
2020-06-18 16:56:03 -04:00
)
}
Ok(())
}
fn parse_supports(&mut self) -> SassResult<Stmt> {
2020-06-20 15:52:53 -04:00
let params = self.parse_media_args()?;
if params.is_empty() {
return Err(("Expected \"not\".", self.span_before).into());
}
2020-07-25 18:33:59 -04:00
let raw_body = self.parse_stmt()?;
2020-06-20 15:52:53 -04:00
let mut rules = Vec::with_capacity(raw_body.len());
let mut body = Vec::new();
for stmt in raw_body {
match stmt {
Stmt::Style(..) => body.push(stmt),
_ => rules.push(stmt),
}
}
if !self.super_selectors.last().is_empty() {
body = vec![Stmt::RuleSet {
selector: ExtendedSelector::new(self.super_selectors.last().clone().0),
2020-06-20 15:52:53 -04:00
body,
}];
}
body.append(&mut rules);
2020-06-25 00:27:24 -04:00
Ok(Stmt::Supports(Box::new(SupportsRule {
2020-06-20 15:52:53 -04:00
params: params.trim().to_owned(),
body,
2020-06-25 00:27:24 -04:00
})))
}
2020-06-20 15:52:53 -04:00
// todo: we should use a specialized struct to represent these
fn parse_media_args(&mut self) -> SassResult<String> {
let mut params = String::new();
self.whitespace();
while let Some(tok) = self.toks.next() {
match tok.kind {
'{' => break,
'#' => {
if let Some(Token { kind: '{', pos }) = self.toks.peek().cloned() {
self.toks.next();
self.span_before = pos;
let interpolation = self.parse_interpolation()?;
params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
continue;
} else {
params.push(tok.kind);
}
}
'\n' | ' ' | '\t' => {
self.whitespace();
params.push(' ');
continue;
}
_ => {}
}
params.push(tok.kind);
}
Ok(params)
}
2020-06-16 19:38:30 -04:00
}
impl<'a> Parser<'a> {
fn debug(&self, message: &Spanned<Cow<'a, str>>) {
2020-07-15 13:40:39 -04:00
if self.options.quiet {
return;
}
2020-06-16 19:38:30 -04:00
let loc = self.map.look_up_span(message.span);
eprintln!(
"{}:{} Debug: {}",
loc.file.name(),
loc.begin.line + 1,
message.node
);
}
fn warn(&self, message: &Spanned<Cow<'a, str>>) {
2020-07-15 13:40:39 -04:00
if self.options.quiet {
return;
}
2020-06-16 19:38:30 -04:00
let loc = self.map.look_up_span(message.span);
eprintln!(
"Warning: {}\n {} {}:{} root stylesheet",
message.node,
loc.file.name(),
loc.begin.line + 1,
loc.begin.column + 1
);
}
}