From e5cceb60ec4ad54569f7ea9cdba82c08e7ac5679 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 24 May 2020 15:30:06 -0400 Subject: [PATCH] track span_before when parsing values this allows us to remove many panics on invalid input --- src/args.rs | 13 +++++-- src/atrule/each_rule.rs | 7 +++- src/atrule/for_rule.rs | 4 +-- src/atrule/function.rs | 13 +++---- src/atrule/if_rule.rs | 16 +++++++-- src/atrule/media.rs | 4 +-- src/atrule/mixin.rs | 6 +--- src/atrule/mod.rs | 3 ++ src/atrule/unknown.rs | 4 +-- src/atrule/while_rule.rs | 4 +-- src/lib.rs | 6 ++-- src/selector/mod.rs | 4 +-- src/style.rs | 12 ++++--- src/stylesheet.rs | 5 +-- src/utils/comment_whitespace.rs | 22 +++++++----- src/utils/interpolation.rs | 10 ++++-- src/utils/strings.rs | 21 ++++++------ src/utils/variables.rs | 5 +-- src/value/css_function.rs | 23 +++++++------ src/value/parse.rs | 60 +++++++++++++++++++++++++-------- tests/error.rs | 16 +++++++++ tests/units.rs | 2 +- 22 files changed, 171 insertions(+), 89 deletions(-) diff --git a/src/args.rs b/src/args.rs index c9f5ffb..04e3f1c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -109,7 +109,10 @@ impl CallArgs { super_selector: &Selector, ) -> Option>> { match self.0.remove(&CallArg::Named(val.into())) { - Some(v) => Some(Value::from_vec(v, scope, super_selector)), + Some(v) => { + let span_before = v[0].pos; + Some(Value::from_vec(v, scope, super_selector, span_before)) + } None => None, } } @@ -124,7 +127,10 @@ impl CallArgs { super_selector: &Selector, ) -> Option>> { match self.0.remove(&CallArg::Positional(val)) { - Some(v) => Some(Value::from_vec(v, scope, super_selector)), + Some(v) => { + let span_before = v[0].pos; + Some(Value::from_vec(v, scope, super_selector, span_before)) + } None => None, } } @@ -159,7 +165,8 @@ impl CallArgs { }; args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2)); for arg in args { - vals.push(Value::from_vec(arg.1, scope, super_selector)?); + let span_before = arg.1[0].pos; + vals.push(Value::from_vec(arg.1, scope, super_selector, span_before)?); } Ok(vals) } diff --git a/src/atrule/each_rule.rs b/src/atrule/each_rule.rs index 1148a1a..15ed6a4 100644 --- a/src/atrule/each_rule.rs +++ b/src/atrule/each_rule.rs @@ -119,7 +119,12 @@ pub(crate) fn parse_each>( return Err(("Expected \"in\".", i.span).into()); } devour_whitespace(toks); - let iter_val = Value::from_vec(read_until_open_curly_brace(toks)?, scope, super_selector)?; + let iter_val = Value::from_vec( + read_until_open_curly_brace(toks)?, + scope, + super_selector, + i.span, + )?; let iter = match iter_val.node.eval(iter_val.span)?.node { Value::List(v, ..) => v, Value::Map(m) => m diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index da62996..46160c2 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -133,7 +133,7 @@ pub(crate) fn parse_for>( } } devour_whitespace(toks); - let from_val = Value::from_vec(from_toks, scope, super_selector)?; + let from_val = Value::from_vec(from_toks, scope, super_selector, span_before)?; let from = match from_val.node.eval(from_val.span)?.node { Value::Dimension(n, _) => match n.to_integer().to_isize() { Some(v) => v, @@ -150,7 +150,7 @@ pub(crate) fn parse_for>( let to_toks = read_until_open_curly_brace(toks)?; toks.next(); - let to_val = Value::from_vec(to_toks, scope, super_selector)?; + let to_val = Value::from_vec(to_toks, scope, super_selector, from_val.span)?; let to = match to_val.node.eval(to_val.span)?.node { Value::Dimension(n, _) => match n.to_integer().to_isize() { Some(v) => v, diff --git a/src/atrule/function.rs b/src/atrule/function.rs index 2da1e15..81942ec 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -88,11 +88,7 @@ impl Function { let val = match args.get(idx, arg.name.clone(), &scope, super_selector) { Some(v) => v?, None => match arg.default.as_mut() { - Some(v) => Value::from_tokens( - &mut mem::take(v).into_iter().peekmore(), - &scope, - super_selector, - )?, + Some(v) => Value::from_vec(mem::take(v), &scope, super_selector, args.span())?, None => { return Err( (format!("Missing argument ${}.", &arg.name), args.span()).into() @@ -137,7 +133,7 @@ impl Function { match stmt.node { Stmt::AtRule(AtRule::Return(toks)) => { return Ok(Some( - Value::from_vec(toks, &self.scope, super_selector)?.node, + Value::from_vec(toks, &self.scope, super_selector, stmt.span)?.node, )); } Stmt::AtRule(AtRule::For(f)) => { @@ -169,7 +165,8 @@ impl Function { } Stmt::AtRule(AtRule::While(w)) => { let scope = &mut self.scope.clone(); - let mut val = Value::from_vec(w.cond.clone(), scope, super_selector)?; + let mut val = + Value::from_vec(w.cond.clone(), scope, super_selector, stmt.span)?; while val.node.is_true(val.span)? { let while_stmts = eat_stmts( &mut w.body.clone().into_iter().peekmore(), @@ -181,7 +178,7 @@ impl Function { if let Some(v) = self.call(super_selector, while_stmts)? { return Ok(Some(v)); } - val = Value::from_vec(w.cond.clone(), scope, super_selector)?; + val = Value::from_vec(w.cond.clone(), scope, super_selector, val.span)?; } } Stmt::AtRule(AtRule::Each(..)) => todo!("@each in @function"), diff --git a/src/atrule/if_rule.rs b/src/atrule/if_rule.rs index b6df180..b24ba5c 100644 --- a/src/atrule/if_rule.rs +++ b/src/atrule/if_rule.rs @@ -42,10 +42,19 @@ impl If { devour_whitespace_or_comment(toks)?; let mut branches = Vec::new(); let init_cond_toks = read_until_open_curly_brace(toks)?; - if init_cond_toks.is_empty() || toks.next().is_none() { + if init_cond_toks.is_empty() { return Err(("Expected expression.", span_before).into()); } - let init_cond = Value::from_vec(init_cond_toks, scope, super_selector)?; + let span_before = match toks.next() { + Some(t) => t.pos, + None => return Err(("Expected expression.", span_before).into()), + }; + let init_cond = Value::from_vec( + init_cond_toks, + scope, + super_selector, + span_before, + )?; devour_whitespace_or_comment(toks)?; let mut init_toks = read_until_closing_curly_brace(toks)?; if let Some(tok) = toks.next() { @@ -78,11 +87,12 @@ impl If { devour_whitespace(toks); match tok.kind.to_ascii_lowercase() { 'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => { - toks.next(); + let pos = toks.next().unwrap().pos; let cond = Value::from_vec( read_until_open_curly_brace(toks)?, scope, super_selector, + pos, )?; toks.next(); devour_whitespace(toks); diff --git a/src/atrule/media.rs b/src/atrule/media.rs index fdd7092..70dd2d1 100644 --- a/src/atrule/media.rs +++ b/src/atrule/media.rs @@ -29,9 +29,9 @@ impl Media { match tok.kind { '{' => break, '#' => { - if toks.peek().unwrap().kind == '{' { + if let Some(Token { kind: '{', pos }) = toks.peek().cloned() { toks.next(); - let interpolation = parse_interpolation(toks, scope, super_selector)?; + let interpolation = parse_interpolation(toks, scope, super_selector, pos)?; params.push_str(&interpolation.node.to_css_string(interpolation.span)?); continue; } else { diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 485a33e..5a82ab5 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -79,11 +79,7 @@ impl Mixin { let val = match args.get(idx, arg.name.clone(), scope, super_selector) { Some(v) => v?, None => match arg.default.as_mut() { - Some(v) => Value::from_tokens( - &mut std::mem::take(v).into_iter().peekmore(), - scope, - super_selector, - )?, + Some(v) => Value::from_vec(mem::take(v), scope, super_selector, args.span())?, None => { return Err( (format!("Missing argument ${}.", &arg.name), args.span()).into() diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 6b311fc..e883c0c 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -72,6 +72,7 @@ impl AtRule { read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, + kind_span, )?; return Err((message.inspect(span)?.to_string(), span.merge(kind_span)).into()); @@ -84,6 +85,7 @@ impl AtRule { read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, + kind_span, )?; span.merge(kind_span); if toks.peek().unwrap().kind == ';' { @@ -106,6 +108,7 @@ impl AtRule { read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, + kind_span, )?; span.merge(kind_span); if toks.peek().unwrap().kind == ';' { diff --git a/src/atrule/unknown.rs b/src/atrule/unknown.rs index 3f62fe1..0fc4c0e 100644 --- a/src/atrule/unknown.rs +++ b/src/atrule/unknown.rs @@ -41,9 +41,9 @@ impl UnknownAtRule { match tok.kind { '{' => break, '#' => { - if let Some(Token { kind: '{', .. }) = toks.peek() { + if let Some(Token { kind: '{', pos }) = toks.peek().cloned() { toks.next(); - let interpolation = parse_interpolation(toks, scope, super_selector)?; + let interpolation = parse_interpolation(toks, scope, super_selector, pos)?; params.push_str(&interpolation.node.to_css_string(interpolation.span)?); } else { params.push(tok.kind); diff --git a/src/atrule/while_rule.rs b/src/atrule/while_rule.rs index b1903f3..59e4eb6 100644 --- a/src/atrule/while_rule.rs +++ b/src/atrule/while_rule.rs @@ -28,7 +28,7 @@ impl While { content: Option<&[Spanned]>, ) -> SassResult>> { let mut stmts = Vec::new(); - let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?; + let mut val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?; let scope = &mut scope.clone(); while val.node.is_true(val.span)? { ruleset_eval( @@ -39,7 +39,7 @@ impl While { content, &mut stmts, )?; - val = Value::from_vec(self.cond.clone(), scope, super_selector)?; + val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?; } Ok(stmts) } diff --git a/src/lib.rs b/src/lib.rs index 8d945a1..9b51d48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -232,7 +232,7 @@ pub(crate) fn eat_expr>( String::new(), span_before, )?; - let value = Style::parse_value(&mut v, scope, super_selector)?; + let value = Style::parse_value(&mut v, scope, super_selector, span_before)?; return Ok(Some(Spanned { node: Expr::Style(Box::new(Style { property, value })), span, @@ -259,7 +259,7 @@ pub(crate) fn eat_expr>( String::new(), tok.pos, )?; - let value = Style::parse_value(&mut v, scope, super_selector)?; + let value = Style::parse_value(&mut v, scope, super_selector, tok.pos)?; return Ok(Some(Spanned { node: Expr::Style(Box::new(Style { property, value })), span, @@ -298,7 +298,7 @@ pub(crate) fn eat_expr>( val, default, global, - } = eat_variable_value(toks, scope, super_selector)?; + } = eat_variable_value(toks, scope, super_selector, name.span)?; if global { insert_global_var(&name.node, val.clone())?; } diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 06e636e..5df064f 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -233,10 +233,10 @@ impl Selector { span = span.merge(tok.pos()); match tok.kind { '#' => { - if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { + if let Some(Token { kind: '{', pos }) = toks.peek().cloned() { toks.next(); string.push_str( - &parse_interpolation(toks, scope, super_selector)? + &parse_interpolation(toks, scope, super_selector, pos)? .to_css_string(span)?, ); } else { diff --git a/src/style.rs b/src/style.rs index 84e9220..fb8a7af 100644 --- a/src/style.rs +++ b/src/style.rs @@ -49,8 +49,9 @@ impl Style { toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult> { - StyleParser::new(scope, super_selector).parse_style_value(toks, scope) + StyleParser::new(scope, super_selector).parse_style_value(toks, scope, span_before) } pub fn from_tokens>( @@ -80,9 +81,10 @@ impl<'a> StyleParser<'a> { &self, toks: &mut PeekMoreIterator, scope: &Scope, + span_before: Span, ) -> SassResult> { devour_whitespace(toks); - Value::from_tokens(toks, scope, self.super_selector) + Value::from_tokens(toks, scope, self.super_selector, span_before) } pub(crate) fn eat_style_group>( @@ -93,7 +95,7 @@ impl<'a> StyleParser<'a> { ) -> SassResult { let mut styles = Vec::new(); devour_whitespace(toks); - while let Some(tok) = toks.peek() { + while let Some(tok) = toks.peek().cloned() { match tok.kind { '{' => { let span_before = toks.next().unwrap().pos; @@ -121,7 +123,7 @@ impl<'a> StyleParser<'a> { continue; } } - let value = self.parse_style_value(toks, scope)?; + let value = self.parse_style_value(toks, scope, span_before)?; match toks.peek().unwrap().kind { '}' => { styles.push(Style { property, value }); @@ -160,7 +162,7 @@ impl<'a> StyleParser<'a> { } } _ => { - let value = self.parse_style_value(toks, scope)?; + let value = self.parse_style_value(toks, scope, tok.pos)?; let t = toks.peek().ok_or(("expected more input.", value.span))?; match t.kind { '}' => {} diff --git a/src/stylesheet.rs b/src/stylesheet.rs index e20927c..48e2365 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -195,14 +195,15 @@ impl<'a> StyleSheetParser<'a> { let whitespace = peek_whitespace(self.lexer); match self.lexer.peek() { - Some(Token { kind: ':', .. }) => { + Some(Token { kind: ':', pos }) => { + let pos = *pos; self.lexer .take(name.node.chars().count() + whitespace + 1) .for_each(drop); devour_whitespace(self.lexer); let VariableDecl { val, default, .. } = - eat_variable_value(self.lexer, &Scope::new(), &Selector::new())?; + eat_variable_value(self.lexer, &Scope::new(), &Selector::new(), pos)?; if !(default && global_var_exists(&name.node)) { insert_global_var(&name.node, val)?; diff --git a/src/utils/comment_whitespace.rs b/src/utils/comment_whitespace.rs index 1b8aeda..bd2e1ec 100644 --- a/src/utils/comment_whitespace.rs +++ b/src/utils/comment_whitespace.rs @@ -100,16 +100,20 @@ pub(crate) fn eat_comment>( }; while let Some(tok) = toks.next() { span = span.merge(tok.pos()); - if tok.kind == '*' && toks.peek().unwrap().kind == '/' { - toks.next(); - break; - } else if tok.kind == '#' && toks.peek().unwrap().kind == '{' { - toks.next(); - comment - .push_str(&parse_interpolation(toks, scope, super_selector)?.to_css_string(span)?); - continue; + match (tok.kind, toks.peek()) { + ('*', Some(Token { kind: '/', .. })) => { + toks.next(); + break; + } + ('#', Some(Token { kind: '{', .. })) => { + toks.next(); + comment.push_str( + &parse_interpolation(toks, scope, super_selector, span)?.to_css_string(span)?, + ); + continue; + } + (..) => comment.push(tok.kind), } - comment.push(tok.kind); } devour_whitespace(toks); Ok(Spanned { diff --git a/src/utils/interpolation.rs b/src/utils/interpolation.rs index c21fe90..ba18e8c 100644 --- a/src/utils/interpolation.rs +++ b/src/utils/interpolation.rs @@ -1,6 +1,6 @@ use std::iter::Iterator; -use codemap::Spanned; +use codemap::{Span, Spanned}; use peekmore::PeekMoreIterator; @@ -15,8 +15,14 @@ pub(crate) fn parse_interpolation>( toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult> { - let val = Value::from_vec(read_until_closing_curly_brace(toks)?, scope, super_selector)?; + let val = Value::from_vec( + read_until_closing_curly_brace(toks)?, + scope, + super_selector, + span_before, + )?; toks.next(); Ok(Spanned { node: val.node.eval(val.span)?.node.unquote(), diff --git a/src/utils/strings.rs b/src/utils/strings.rs index 27351c6..b49d8c6 100644 --- a/src/utils/strings.rs +++ b/src/utils/strings.rs @@ -97,11 +97,11 @@ fn interpolated_ident_body>( buf.push_str(&escape(toks, false)?); } '#' => { - if let Some(Token { kind: '{', .. }) = toks.peek_forward(1) { + if let Some(Token { kind: '{', pos }) = toks.peek_forward(1).cloned() { toks.next(); toks.next(); // TODO: if ident, interpolate literally - let interpolation = parse_interpolation(toks, scope, super_selector)?; + let interpolation = parse_interpolation(toks, scope, super_selector, pos)?; buf.push_str(&interpolation.node.to_css_string(interpolation.span)?); } else { toks.reset_view(); @@ -207,20 +207,21 @@ pub(crate) fn eat_ident>( // (first == '#' && scanner.peekChar(1) == $lbrace) } else if first == '#' { toks.next(); - if toks.peek().is_none() { + let Token { kind, pos } = if let Some(tok) = toks.peek() { + *tok + } else { return Err(("Expected identifier.", pos).into()); - } - let Token { kind, pos } = toks.peek().unwrap(); - if kind == &'{' { + }; + if kind == '{' { toks.next(); text.push_str( - &match parse_interpolation(toks, scope, super_selector)?.node { + &match parse_interpolation(toks, scope, super_selector, pos)?.node { Value::String(s, ..) => s, v => v.to_css_string(span)?.into(), }, ); } else { - return Err(("Expected identifier.", *pos).into()); + return Err(("Expected identifier.", pos).into()); } } else { return Err(("Expected identifier.", pos).into()); @@ -293,9 +294,9 @@ pub(crate) fn parse_quoted_string>( '"' if q == '"' => break, '\'' if q == '\'' => break, '#' => { - if toks.peek().unwrap().kind == '{' { + if let Some(Token { kind: '{', pos }) = toks.peek().cloned() { toks.next(); - let interpolation = parse_interpolation(toks, scope, super_selector)?; + let interpolation = parse_interpolation(toks, scope, super_selector, pos)?; s.push_str(&match interpolation.node { Value::String(s, ..) => s, v => v.to_css_string(interpolation.span)?.into(), diff --git a/src/utils/variables.rs b/src/utils/variables.rs index d9bc6fd..ce91819 100644 --- a/src/utils/variables.rs +++ b/src/utils/variables.rs @@ -1,6 +1,6 @@ use std::iter::Iterator; -use codemap::Spanned; +use codemap::{Span, Spanned}; use peekmore::PeekMoreIterator; @@ -34,6 +34,7 @@ pub(crate) fn eat_variable_value>( toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult { devour_whitespace(toks); let mut default = false; @@ -126,6 +127,6 @@ pub(crate) fn eat_variable_value>( } } devour_whitespace(toks); - let val = Value::from_vec(val_toks, scope, super_selector)?; + let val = Value::from_vec(val_toks, scope, super_selector, span_before)?; Ok(VariableDecl::new(val, default, global)) } diff --git a/src/value/css_function.rs b/src/value/css_function.rs index 98e3049..d20e848 100644 --- a/src/value/css_function.rs +++ b/src/value/css_function.rs @@ -1,3 +1,5 @@ +use codemap::{Span, Spanned}; + use peekmore::PeekMoreIterator; use crate::error::SassResult; @@ -7,6 +9,7 @@ use crate::utils::{ devour_whitespace, parse_interpolation, peek_escape, peek_until_closing_curly_brace, peek_whitespace, }; +use crate::value::Value; use crate::Token; pub(crate) fn eat_calc_args>( @@ -26,10 +29,10 @@ pub(crate) fn eat_calc_args>( } '#' => { if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { - let span = toks.next().unwrap().pos(); - buf.push_str( - &parse_interpolation(toks, scope, super_selector)?.to_css_string(span)?, - ); + let span_before = toks.next().unwrap().pos(); + let interpolation = + parse_interpolation(toks, scope, super_selector, span_before)?; + buf.push_str(&interpolation.node.to_css_string(interpolation.span)?); } else { buf.push('#'); } @@ -106,11 +109,11 @@ pub(crate) fn try_eat_url>( } else if kind == '\\' { buf.push_str(&peek_escape(toks)?); } else if kind == '#' { - let next = toks.peek(); - if next.is_some() && next.unwrap().kind == '{' { + if let Some(Token { kind: '{', pos }) = toks.peek() { + let pos = *pos; toks.move_forward(1); peek_counter += 1; - let (interpolation, count) = peek_interpolation(toks, scope, super_selector)?; + let (interpolation, count) = peek_interpolation(toks, scope, super_selector, pos)?; peek_counter += count; buf.push_str(&match interpolation.node { Value::String(s, ..) => s, @@ -144,18 +147,16 @@ pub(crate) fn try_eat_url>( Ok(None) } -use crate::value::Value; -use codemap::Spanned; - fn peek_interpolation>( toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult<(Spanned, usize)> { let vec = peek_until_closing_curly_brace(toks); let peek_counter = vec.len(); toks.move_forward(1); - let val = Value::from_vec(vec, scope, super_selector)?; + let val = Value::from_vec(vec, scope, super_selector, span_before)?; Ok(( Spanned { node: val.node.eval(val.span)?.node.unquote(), diff --git a/src/value/parse.rs b/src/value/parse.rs index b81670b..9885dc9 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -189,7 +189,12 @@ fn parse_paren( let paren_toks = &mut t.node.into_iter().peekmore(); let mut map = SassMap::new(); - let key = Value::from_vec(read_until_char(paren_toks, ':')?, scope, super_selector)?; + let key = Value::from_vec( + read_until_char(paren_toks, ':')?, + scope, + super_selector, + t.span, + )?; if paren_toks.peek().is_none() { return Ok(Spanned { @@ -198,7 +203,12 @@ fn parse_paren( }); } - let val = Value::from_vec(read_until_char(paren_toks, ',')?, scope, super_selector)?; + let val = Value::from_vec( + read_until_char(paren_toks, ',')?, + scope, + super_selector, + key.span, + )?; map.insert(key.node, val.node); @@ -212,9 +222,19 @@ fn parse_paren( let mut span = key.span; loop { - let key = Value::from_vec(read_until_char(paren_toks, ':')?, scope, super_selector)?; + let key = Value::from_vec( + read_until_char(paren_toks, ':')?, + scope, + super_selector, + span, + )?; devour_whitespace(paren_toks); - let val = Value::from_vec(read_until_char(paren_toks, ',')?, scope, super_selector)?; + let val = Value::from_vec( + read_until_char(paren_toks, ',')?, + scope, + super_selector, + key.span, + )?; span = span.merge(val.span); devour_whitespace(paren_toks); if map.insert(key.node, val.node) { @@ -387,7 +407,7 @@ fn single_value>( IntermediateValue::Whitespace => unreachable!(), IntermediateValue::Comma => return Err(("Expected expression.", span).into()), IntermediateValue::Bracketed(t) => { - let v = Value::from_vec(t, scope, super_selector)?; + let v = Value::from_vec(t, scope, super_selector, span)?; match v.node { Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed), v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed), @@ -416,10 +436,11 @@ impl Value { toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult> { let span = match toks.peek() { Some(Token { pos, .. }) => *pos, - None => todo!("Expected expression."), + None => return Err(("Expected expression.", span_before).into()), }; devour_whitespace(toks); let mut last_was_whitespace = false; @@ -490,13 +511,15 @@ impl Value { ); continue; } - space_separated.push(match Value::from_vec(t, scope, super_selector)?.node { - Value::List(v, sep, Brackets::None) => { - Value::List(v, sep, Brackets::Bracketed).span(val.span) - } - v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed) - .span(val.span), - }) + space_separated.push( + match Value::from_vec(t, scope, super_selector, val.span)?.node { + Value::List(v, sep, Brackets::None) => { + Value::List(v, sep, Brackets::Bracketed).span(val.span) + } + v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed) + .span(val.span), + }, + ) } IntermediateValue::Paren(t) => { last_was_whitespace = false; @@ -547,8 +570,17 @@ impl Value { toks: Vec, scope: &Scope, super_selector: &Selector, + span_before: Span, ) -> SassResult> { - Self::from_tokens(&mut toks.into_iter().peekmore(), scope, super_selector) + if toks.is_empty() { + return Err(("Expected expression.", span_before).into()); + } + Self::from_tokens( + &mut toks.into_iter().peekmore(), + scope, + super_selector, + span_before, + ) } fn ident>( diff --git a/tests/error.rs b/tests/error.rs index 25c10ea..2ba9152 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -208,3 +208,19 @@ error!( at_else_alone, "@else {}", "Error: This at-rule is not allowed here." ); +error!( + no_expression_for_variable, + "a {$color: {ed;}", "Error: Expected expression." +); +error!( + empty_style_value_no_semicolon, + "a {color:}", "Error: Expected expression." +); +error!( + empty_style_value_semicolon, + "a {color:;}", "Error: Expected expression." +); +error!( + ident_colon_closing_brace, + "r:}", "Error: Expected expression." +); diff --git a/tests/units.rs b/tests/units.rs index 6fbcc6a..a1063c8 100644 --- a/tests/units.rs +++ b/tests/units.rs @@ -87,7 +87,7 @@ test!( test!( non_ascii_numeric_interpreted_as_unit, "a {\n color: 2߄;\n}\n", - "@charset \"UTF-8\";\na {\n color: 2߄;\n}" + "@charset \"UTF-8\";\na {\n color: 2߄;\n}\n" ); error!( display_single_mul,