more robust handling of empty input after resolving interpolation

This commit is contained in:
Connor Skees 2022-12-28 14:41:36 -05:00
parent bba405392c
commit bb937ae84f
8 changed files with 63 additions and 26 deletions

View File

@ -54,7 +54,7 @@ impl MediaQuery {
}
pub fn parse_list(list: &str, span: Span) -> SassResult<Vec<Self>> {
let toks = Lexer::new(list.chars().map(|x| Token::new(span, x)).collect());
let toks = Lexer::new(list.chars().map(|x| Token::new(span, x)).collect(), span);
MediaQueryParser::new(toks).parse()
}

View File

@ -204,7 +204,7 @@ pub(crate) struct AstExtendRule {
#[derive(Debug, Clone)]
pub(crate) struct AstAtRootRule {
pub children: Vec<AstStmt>,
pub query: Option<Interpolation>,
pub query: Option<Spanned<Interpolation>>,
#[allow(unused)]
pub span: Span,
}

View File

@ -969,14 +969,14 @@ impl<'a> Visitor<'a> {
fn visit_at_root_rule(&mut self, mut at_root_rule: AstAtRootRule) -> SassResult<Option<Value>> {
let query = match at_root_rule.query.clone() {
Some(val) => {
let resolved = self.perform_interpolation(val, true)?;
Some(query) => {
let resolved = self.perform_interpolation(query.node, true)?;
let span = query.span;
let query_toks = Lexer::new(
resolved
.chars()
.map(|x| Token::new(self.span_before, x))
.collect(),
resolved.chars().map(|x| Token::new(span, x)).collect(),
span,
);
AtRootQueryParser::new(query_toks).parse()?
@ -1137,7 +1137,10 @@ impl<'a> Visitor<'a> {
allows_placeholder: bool,
span: Span,
) -> SassResult<SelectorList> {
let sel_toks = Lexer::new(selector_text.chars().map(|x| Token::new(span, x)).collect());
let sel_toks = Lexer::new(
selector_text.chars().map(|x| Token::new(span, x)).collect(),
span,
);
SelectorParser::new(sel_toks, allows_parent, allows_placeholder, span).parse()
}
@ -2742,11 +2745,10 @@ impl<'a> Visitor<'a> {
let selector_text = self.interpolation_to_value(ruleset_selector, true, true)?;
if self.flags.in_keyframes() {
let span = ruleset.selector_span;
let sel_toks = Lexer::new(
selector_text
.chars()
.map(|x| Token::new(self.span_before, x))
.collect(),
selector_text.chars().map(|x| Token::new(span, x)).collect(),
span,
);
let parsed_selector =
KeyframesSelectorParser::new(sel_toks).parse_keyframes_selector()?;

View File

@ -7,8 +7,11 @@ use crate::Token;
const FORM_FEED: char = '\x0C';
#[derive(Debug, Clone)]
// todo: remove lifetime as Cow is now superfluous
pub(crate) struct Lexer<'a> {
buf: Cow<'a, [Token]>,
/// The span to be used in the case that `buf` is empty
empty_span: Span,
cursor: usize,
}
@ -37,19 +40,23 @@ impl<'a> Lexer<'a> {
}
pub fn prev_span(&self) -> Span {
self.buf
.get(self.cursor.saturating_sub(1))
.copied()
.unwrap_or_else(|| self.buf.last().copied().unwrap())
.pos
match self.buf.get(self.cursor.saturating_sub(1)) {
Some(tok) => tok.pos,
None => match self.buf.last() {
Some(tok) => tok.pos,
None => self.empty_span,
},
}
}
pub fn current_span(&self) -> Span {
self.buf
.get(self.cursor)
.copied()
.unwrap_or_else(|| self.buf.last().copied().unwrap())
.pos
match self.buf.get(self.cursor) {
Some(tok) => tok.pos,
None => match self.buf.last() {
Some(tok) => tok.pos,
None => self.empty_span,
},
}
}
pub fn peek(&self) -> Option<Token> {
@ -131,13 +138,14 @@ impl<'a> Lexer<'a> {
}
.collect();
Self::new(buf)
Self::new(buf, file.span.subspan(0, 0))
}
pub fn new(buf: Vec<Token>) -> Self {
pub fn new(buf: Vec<Token>, empty_span: Span) -> Self {
Lexer {
buf: Cow::Owned(buf),
cursor: 0,
empty_span,
}
}
}

View File

@ -325,12 +325,17 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
fn parse_at_root_rule(&mut self, start: usize) -> SassResult<AstStmt> {
Ok(AstStmt::AtRootRule(if self.toks_mut().next_char_is('(') {
let query_start = self.toks().cursor();
let query = self.parse_at_root_query()?;
let query_span = self.toks_mut().span_from(query_start);
self.whitespace()?;
let children = self.with_children(Self::parse_statement)?.node;
AstAtRootRule {
query: Some(query),
query: Some(Spanned {
node: query,
span: query_span,
}),
children,
span: self.toks_mut().span_from(start),
}
@ -1521,6 +1526,7 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
.chars()
.map(|x| Token::new(self.span_before(), x))
.collect(),
self.span_before(),
);
// if namespace is empty, avoid attempting to parse an identifier from

View File

@ -279,3 +279,12 @@ error!(
}",
"Error: @extend may only be used within style rules."
);
error!(
selector_is_empty_after_interpolation_is_resolved,
"@at-root #{null} {}", "Error: expected selector."
);
error!(
// todo: dart-sass gives error r#"Error: Expected "with" or "without"."#
query_is_empty_parens_after_interpolation_is_resolved,
"@at-root (#{null}) {}", r#"Error: Expected "without"."#
);

View File

@ -327,6 +327,13 @@ error!(
}",
r#"Error: Expected digit."#
);
error!(
selector_is_empty_after_interpolation_is_resolved,
"@keyframes foo {
#{null} {}
}",
r#"Error: Expected number."#
);
// todo: span for this
// @keyframes foo {

View File

@ -570,3 +570,8 @@ error!(
}"#,
"Error: expected no more input."
);
error!(
empty_query_after_resolving_interpolation,
"@media #{null} {}",
"Error: expected no more input."
);