From 737a6ba90d6170556839729e637e6f3bf4e8e45f Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 24 May 2020 10:04:30 -0400 Subject: [PATCH] emit proper error on unclosed quote --- src/args.rs | 8 +++--- src/atrule/each_rule.rs | 4 +-- src/atrule/for_rule.rs | 4 +-- src/atrule/function.rs | 2 +- src/atrule/if_rule.rs | 14 +++++----- src/atrule/mixin.rs | 2 +- src/atrule/mod.rs | 14 +++++----- src/atrule/while_rule.rs | 4 +-- src/lib.rs | 4 +-- src/selector/mod.rs | 9 +++++-- src/utils/chars.rs | 7 ++--- src/utils/interpolation.rs | 2 +- src/utils/read_until.rs | 55 ++++++++++++++++++++++++-------------- src/utils/variables.rs | 4 +-- src/value/parse.rs | 18 ++++++++----- tests/if.rs | 2 ++ 16 files changed, 91 insertions(+), 62 deletions(-) diff --git a/src/args.rs b/src/args.rs index 8a86a1c..6f25a6f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -262,7 +262,7 @@ pub(crate) fn eat_func_args>( } '(' => { default.push(toks.next().unwrap()); - default.extend(read_until_closing_paren(toks)); + default.extend(read_until_closing_paren(toks)?); } _ => default.push(toks.next().unwrap()), } @@ -380,15 +380,15 @@ pub(crate) fn eat_call_args>( ',' => break, '[' => { val.push(tok); - val.extend(read_until_closing_square_brace(toks)); + val.extend(read_until_closing_square_brace(toks)?); } '(' => { val.push(tok); - val.extend(read_until_closing_paren(toks)); + val.extend(read_until_closing_paren(toks)?); } '"' | '\'' => { val.push(tok); - val.extend(read_until_closing_quote(toks, tok.kind)); + val.extend(read_until_closing_quote(toks, tok.kind)?); } _ => val.push(tok), } diff --git a/src/atrule/each_rule.rs b/src/atrule/each_rule.rs index 676d908..1148a1a 100644 --- a/src/atrule/each_rule.rs +++ b/src/atrule/each_rule.rs @@ -119,7 +119,7 @@ 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)?; let iter = match iter_val.node.eval(iter_val.span)?.node { Value::List(v, ..) => v, Value::Map(m) => m @@ -130,7 +130,7 @@ pub(crate) fn parse_each>( }; toks.next(); devour_whitespace(toks); - let mut body = read_until_closing_curly_brace(toks); + let mut body = read_until_closing_curly_brace(toks)?; body.push(toks.next().unwrap()); devour_whitespace(toks); diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index 06eec7a..da62996 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -148,7 +148,7 @@ pub(crate) fn parse_for>( } }; - let to_toks = read_until_open_curly_brace(toks); + let to_toks = read_until_open_curly_brace(toks)?; toks.next(); let to_val = Value::from_vec(to_toks, scope, super_selector)?; let to = match to_val.node.eval(to_val.span)?.node { @@ -164,7 +164,7 @@ pub(crate) fn parse_for>( .into()) } }; - let body = read_until_closing_curly_brace(toks); + let body = read_until_closing_curly_brace(toks)?; toks.next(); devour_whitespace(toks); diff --git a/src/atrule/function.rs b/src/atrule/function.rs index 071a2ce..2da1e15 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -58,7 +58,7 @@ impl Function { devour_whitespace(toks); - let mut body = read_until_closing_curly_brace(toks); //eat_stmts(toks, &mut scope.clone(), super_selector)?; + let mut body = read_until_closing_curly_brace(toks)?; //eat_stmts(toks, &mut scope.clone(), super_selector)?; body.push(toks.next().unwrap()); devour_whitespace(toks); diff --git a/src/atrule/if_rule.rs b/src/atrule/if_rule.rs index ed6364c..b6df180 100644 --- a/src/atrule/if_rule.rs +++ b/src/atrule/if_rule.rs @@ -41,13 +41,13 @@ impl If { ) -> SassResult { devour_whitespace_or_comment(toks)?; let mut branches = Vec::new(); - let init_toks = read_until_open_curly_brace(toks); - if init_toks.is_empty() || toks.next().is_none() { + let init_cond_toks = read_until_open_curly_brace(toks)?; + if init_cond_toks.is_empty() || toks.next().is_none() { return Err(("Expected expression.", span_before).into()); } - let init_cond = Value::from_vec(init_toks, scope, super_selector)?; + let init_cond = Value::from_vec(init_cond_toks, scope, super_selector)?; devour_whitespace_or_comment(toks)?; - let mut init_toks = read_until_closing_curly_brace(toks); + let mut init_toks = read_until_closing_curly_brace(toks)?; if let Some(tok) = toks.next() { init_toks.push(tok); } else { @@ -80,18 +80,18 @@ impl If { 'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => { toks.next(); let cond = Value::from_vec( - read_until_open_curly_brace(toks), + read_until_open_curly_brace(toks)?, scope, super_selector, )?; toks.next(); devour_whitespace(toks); - branches.push(Branch::new(cond, read_until_closing_curly_brace(toks))); + branches.push(Branch::new(cond, read_until_closing_curly_brace(toks)?)); toks.next(); devour_whitespace(toks); } '{' => { - else_ = read_until_closing_curly_brace(toks); + else_ = read_until_closing_curly_brace(toks)?; toks.next(); break; } diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index f0e4f82..d729690 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -49,7 +49,7 @@ impl Mixin { devour_whitespace(toks); - let mut body = read_until_closing_curly_brace(toks); + let mut body = read_until_closing_curly_brace(toks)?; body.push(toks.next().unwrap()); Ok(Spanned { diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 4434557..6b311fc 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -69,7 +69,7 @@ impl AtRule { node: message, span, } = Value::from_vec( - read_until_semicolon_or_closing_curly_brace(toks), + read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, )?; @@ -81,7 +81,7 @@ impl AtRule { node: message, span, } = Value::from_vec( - read_until_semicolon_or_closing_curly_brace(toks), + read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, )?; @@ -103,7 +103,7 @@ impl AtRule { node: message, span, } = Value::from_vec( - read_until_semicolon_or_closing_curly_brace(toks), + read_until_semicolon_or_closing_curly_brace(toks)?, scope, super_selector, )?; @@ -139,7 +139,7 @@ impl AtRule { } } AtRuleKind::Return => { - let v = read_until_semicolon_or_closing_curly_brace(toks); + let v = read_until_semicolon_or_closing_curly_brace(toks)?; if toks.peek().unwrap().kind == ';' { toks.next(); } @@ -153,7 +153,7 @@ impl AtRule { let mut selector = &Selector::replace( super_selector, Selector::from_tokens( - &mut read_until_open_curly_brace(toks).into_iter().peekmore(), + &mut read_until_open_curly_brace(toks)?.into_iter().peekmore(), scope, super_selector, )?, @@ -165,7 +165,7 @@ impl AtRule { } toks.next(); devour_whitespace(toks); - let mut body = read_until_closing_curly_brace(toks); + let mut body = read_until_closing_curly_brace(toks)?; body.push(toks.next().unwrap()); devour_whitespace(toks); let mut styles = Vec::new(); @@ -201,7 +201,7 @@ impl AtRule { } } AtRuleKind::Charset => { - read_until_semicolon_or_closing_curly_brace(toks); + read_until_semicolon_or_closing_curly_brace(toks)?; if toks.peek().unwrap().kind == ';' { toks.next(); } diff --git a/src/atrule/while_rule.rs b/src/atrule/while_rule.rs index 16d6db6..b1903f3 100644 --- a/src/atrule/while_rule.rs +++ b/src/atrule/while_rule.rs @@ -50,7 +50,7 @@ pub(crate) fn parse_while>( span: Span, ) -> SassResult> { devour_whitespace(toks); - let cond = read_until_open_curly_brace(toks); + let cond = read_until_open_curly_brace(toks)?; if cond.is_empty() { return Err(("Expected expression.", span).into()); @@ -58,7 +58,7 @@ pub(crate) fn parse_while>( toks.next(); - let mut body = read_until_closing_curly_brace(toks); + let mut body = read_until_closing_curly_brace(toks)?; body.push(toks.next().unwrap()); diff --git a/src/lib.rs b/src/lib.rs index dfc500a..3334327 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -375,7 +375,7 @@ pub(crate) fn eat_expr>( Some(Token { kind: '{', .. }) => { let next = toks.next().unwrap(); values.push(next); - values.extend(read_until_closing_curly_brace(toks)); + values.extend(read_until_closing_curly_brace(toks)?); if let Some(tok) = toks.next() { values.push(tok); } else { @@ -395,7 +395,7 @@ pub(crate) fn eat_expr>( // it is causing us to emit nothing on malformed input '(' => { values.push(toks.next().unwrap()); - values.extend(read_until_closing_paren(toks)); + values.extend(read_until_closing_paren(toks)?); } _ => values.push(toks.next().unwrap()), }; diff --git a/src/selector/mod.rs b/src/selector/mod.rs index bac46f3..06e636e 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -403,7 +403,12 @@ impl Selector { super_selector: &Selector, span_before: Span, ) -> SassResult { - let is_pseudo_element = if toks.peek().ok_or(("Expected identifier.", span_before))?.kind == ':' { + let is_pseudo_element = if toks + .peek() + .ok_or(("Expected identifier.", span_before))? + .kind + == ':' + { toks.next(); true } else { @@ -419,7 +424,7 @@ impl Selector { Ok( if toks.peek().is_some() && toks.peek().unwrap().kind == '(' { toks.next(); - let mut inner_toks = read_until_closing_paren(toks); + let mut inner_toks = read_until_closing_paren(toks)?; inner_toks.pop(); let inner = Selector::from_tokens( &mut inner_toks.into_iter().peekmore(), diff --git a/src/utils/chars.rs b/src/utils/chars.rs index 3eefae8..483f484 100644 --- a/src/utils/chars.rs +++ b/src/utils/chars.rs @@ -4,6 +4,7 @@ use peekmore::PeekMoreIterator; use super::read_until_closing_quote; +use crate::error::SassResult; use crate::Token; /// Reads until the char is found, consuming the char, @@ -11,13 +12,13 @@ use crate::Token; pub(crate) fn read_until_char>( toks: &mut PeekMoreIterator, c: char, -) -> Vec { +) -> SassResult> { let mut v = Vec::new(); while let Some(tok) = toks.next() { match tok.kind { '"' | '\'' => { v.push(tok); - v.extend(read_until_closing_quote(toks, tok.kind)); + v.extend(read_until_closing_quote(toks, tok.kind)?); continue; } t if t == c => break, @@ -25,7 +26,7 @@ pub(crate) fn read_until_char>( } v.push(tok) } - v + Ok(v) } pub(crate) fn hex_char_for(number: u32) -> char { diff --git a/src/utils/interpolation.rs b/src/utils/interpolation.rs index 9154425..c21fe90 100644 --- a/src/utils/interpolation.rs +++ b/src/utils/interpolation.rs @@ -16,7 +16,7 @@ pub(crate) fn parse_interpolation>( scope: &Scope, super_selector: &Selector, ) -> 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)?; toks.next(); Ok(Spanned { node: val.node.eval(val.span)?.node.unquote(), diff --git a/src/utils/read_until.rs b/src/utils/read_until.rs index abe0abb..8fb213b 100644 --- a/src/utils/read_until.rs +++ b/src/utils/read_until.rs @@ -2,6 +2,7 @@ use std::iter::Iterator; use peekmore::PeekMoreIterator; +use crate::error::SassResult; use crate::Token; use super::{devour_whitespace, read_until_newline}; @@ -11,7 +12,7 @@ use super::{devour_whitespace, read_until_newline}; // Does not consume the open curly brace pub(crate) fn read_until_open_curly_brace>( toks: &mut PeekMoreIterator, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); let mut n = 0; while let Some(tok) = toks.peek() { @@ -33,6 +34,11 @@ pub(crate) fn read_until_open_curly_brace>( } continue; } + q @ '"' | q @ '\'' => { + t.push(toks.next().unwrap()); + t.extend(read_until_closing_quote(toks, q)?); + continue; + } _ => {} } if n == 1 { @@ -41,19 +47,19 @@ pub(crate) fn read_until_open_curly_brace>( t.push(toks.next().unwrap()); } - t + Ok(t) } pub(crate) fn read_until_closing_curly_brace>( toks: &mut PeekMoreIterator, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); let mut nesting = 0; while let Some(tok) = toks.peek() { match tok.kind { q @ '"' | q @ '\'' => { t.push(toks.next().unwrap()); - t.extend(read_until_closing_quote(toks, q)); + t.extend(read_until_closing_quote(toks, q)?); } '{' => { nesting += 1; @@ -80,7 +86,7 @@ pub(crate) fn read_until_closing_curly_brace>( } '(' => { t.push(toks.next().unwrap()); - t.extend(read_until_closing_paren(toks)); + t.extend(read_until_closing_paren(toks)?); } '\\' => { t.push(toks.next().unwrap()); @@ -92,13 +98,16 @@ pub(crate) fn read_until_closing_curly_brace>( } } devour_whitespace(toks); - t + Ok(t) } +/// Read tokens into a vector until a matching closing quote is found +/// +/// The closing quote is included in the output pub(crate) fn read_until_closing_quote>( toks: &mut PeekMoreIterator, q: char, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); while let Some(tok) = toks.next() { match tok.kind { @@ -121,18 +130,24 @@ pub(crate) fn read_until_closing_quote>( let next = toks.peek().unwrap(); if next.kind == '{' { t.push(toks.next().unwrap()); - t.append(&mut read_until_closing_curly_brace(toks)); + t.append(&mut read_until_closing_curly_brace(toks)?); } } _ => t.push(tok), } } - t + if let Some(tok) = t.pop() { + if tok.kind != q { + return Err((format!("Expected {}.", q), tok.pos).into()); + } + t.push(tok); + } + Ok(t) } pub(crate) fn read_until_semicolon_or_closing_curly_brace>( toks: &mut PeekMoreIterator, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); let mut nesting = 0; while let Some(tok) = toks.peek() { @@ -149,7 +164,7 @@ pub(crate) fn read_until_semicolon_or_closing_curly_brace { let quote = toks.next().unwrap(); t.push(quote); - t.extend(read_until_closing_quote(toks, quote.kind)); + t.extend(read_until_closing_quote(toks, quote.kind)?); } '{' => { nesting += 1; @@ -178,12 +193,12 @@ pub(crate) fn read_until_semicolon_or_closing_curly_brace>( toks: &mut PeekMoreIterator, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); let mut scope = 0; while let Some(tok) = toks.next() { @@ -191,7 +206,7 @@ pub(crate) fn read_until_closing_paren>( ')' => { if scope < 1 { t.push(tok); - return t; + return Ok(t); } else { scope -= 1; } @@ -199,7 +214,7 @@ pub(crate) fn read_until_closing_paren>( '(' => scope += 1, '"' | '\'' => { t.push(tok); - t.extend(read_until_closing_quote(toks, tok.kind)); + t.extend(read_until_closing_quote(toks, tok.kind)?); continue; } '\\' => { @@ -213,12 +228,12 @@ pub(crate) fn read_until_closing_paren>( } t.push(tok) } - t + Ok(t) } pub(crate) fn read_until_closing_square_brace>( toks: &mut PeekMoreIterator, -) -> Vec { +) -> SassResult> { let mut t = Vec::new(); let mut scope = 0; while let Some(tok) = toks.next() { @@ -227,7 +242,7 @@ pub(crate) fn read_until_closing_square_brace>( ']' => { if scope < 1 { t.push(tok); - return t; + return Ok(t); } else { scope -= 1; } @@ -235,7 +250,7 @@ pub(crate) fn read_until_closing_square_brace>( '[' => scope += 1, '"' | '\'' => { t.push(tok); - t.extend(read_until_closing_quote(toks, tok.kind)); + t.extend(read_until_closing_quote(toks, tok.kind)?); continue; } '\\' => { @@ -248,5 +263,5 @@ pub(crate) fn read_until_closing_square_brace>( } t.push(tok) } - t + Ok(t) } diff --git a/src/utils/variables.rs b/src/utils/variables.rs index 1680498..d9bc6fd 100644 --- a/src/utils/variables.rs +++ b/src/utils/variables.rs @@ -56,7 +56,7 @@ pub(crate) fn eat_variable_value>( '"' | '\'' => { let quote = toks.next().unwrap(); val_toks.push(quote); - val_toks.extend(read_until_closing_quote(toks, quote.kind)); + val_toks.extend(read_until_closing_quote(toks, quote.kind)?); } '#' => { val_toks.push(toks.next().unwrap()); @@ -93,7 +93,7 @@ pub(crate) fn eat_variable_value>( } '(' => { val_toks.push(toks.next().unwrap()); - val_toks.extend(read_until_closing_paren(toks)); + val_toks.extend(read_until_closing_paren(toks)?); } '!' => { let pos = tok.pos(); diff --git a/src/value/parse.rs b/src/value/parse.rs index 53e54b1..6b34b4f 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -189,7 +189,7 @@ 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)?; if paren_toks.peek().is_none() { return Ok(Spanned { @@ -198,7 +198,7 @@ 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)?; map.insert(key.node, val.node); @@ -212,9 +212,9 @@ 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)?; 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)?; span = span.merge(val.span); devour_whitespace(paren_toks); if map.insert(key.node, val.node) { @@ -755,7 +755,10 @@ impl Value { } '(' => { let mut span = toks.next().unwrap().pos(); - let mut inner = read_until_closing_paren(toks); + let mut inner = match read_until_closing_paren(toks) { + Ok(v) => v, + Err(e) => return Some(Err(e.into())), + }; // todo: the above shouldn't eat the closing paren if let Some(last_tok) = inner.pop() { if last_tok.kind != ')' { @@ -794,7 +797,10 @@ impl Value { } '[' => { let mut span = toks.next().unwrap().pos(); - let mut inner = read_until_closing_square_brace(toks); + let mut inner = match read_until_closing_square_brace(toks) { + Ok(v) => v, + Err(e) => return Some(Err(e.into())), + }; if let Some(last_tok) = inner.pop() { if last_tok.kind != ']' { return Some(Err(("expected \"]\".", span).into())); diff --git a/tests/if.rs b/tests/if.rs index 08a12ef..9aa3ecc 100644 --- a/tests/if.rs +++ b/tests/if.rs @@ -126,3 +126,5 @@ error!( nothing_after_escape, "@if \\", "Error: Expected expression." ); +error!(unclosed_dbl_quote, "@if true \" {}", "Error: Expected \"."); +error!(unclosed_sgl_quote, "@if true ' {}", "Error: Expected '.");