From 3051cec45a2eb3ea290bc7929fdfe61d69924d7f Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 24 May 2020 12:47:04 -0400 Subject: [PATCH] disallow standalone @else --- src/atrule/kind.rs | 17 +++++++++++++---- src/lib.rs | 5 +++-- src/stylesheet.rs | 17 ++++++----------- tests/error.rs | 9 ++++++++- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/atrule/kind.rs b/src/atrule/kind.rs index b24f538..d54d144 100644 --- a/src/atrule/kind.rs +++ b/src/atrule/kind.rs @@ -1,3 +1,9 @@ +use std::convert::TryFrom; + +use codemap::Spanned; + +use crate::error::SassError; + #[derive(Clone, Debug, Eq, PartialEq)] pub enum AtRuleKind { // SASS specific @rules @@ -57,9 +63,10 @@ pub enum AtRuleKind { Unknown(String), } -impl From<&str> for AtRuleKind { - fn from(c: &str) -> Self { - match c.to_ascii_lowercase().as_str() { +impl TryFrom<&Spanned> for AtRuleKind { + type Error = SassError; + fn try_from(c: &Spanned) -> Result { + Ok(match c.node.to_ascii_lowercase().as_str() { "use" => Self::Use, "forward" => Self::Forward, "import" => Self::Import, @@ -81,7 +88,9 @@ impl From<&str> for AtRuleKind { "keyframes" => Self::Keyframes, "content" => Self::Content, "media" => Self::Media, + "else" => return Err(("This at-rule is not allowed here.", c.span).into()), + "" => return Err(("Expected identifier.", c.span).into()), s => Self::Unknown(s.to_owned()), - } + }) } } diff --git a/src/lib.rs b/src/lib.rs index 3334327..8d945a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ grass input.scss #![cfg_attr(feature = "nightly", feature(track_caller))] #![cfg_attr(feature = "profiling", inline(never))] +use std::convert::TryFrom; use std::iter::Iterator; use codemap::{Span, Spanned}; @@ -337,10 +338,10 @@ pub(crate) fn eat_expr>( } '@' => { let span = toks.next().unwrap().pos(); - let Spanned { node: ident, span } = eat_ident(toks, scope, super_selector, span)?; + let rule = eat_ident(toks, scope, super_selector, span)?; devour_whitespace(toks); let rule = AtRule::from_tokens( - AtRuleKind::from(ident.as_str()), + AtRuleKind::try_from(&rule)?, span, toks, scope, diff --git a/src/stylesheet.rs b/src/stylesheet.rs index c5df0a8..e20927c 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::fs; use std::iter::Iterator; use std::path::Path; @@ -226,20 +227,14 @@ impl<'a> StyleSheetParser<'a> { } '@' => { let span_before = self.lexer.next().unwrap().pos(); - let Spanned { - node: at_rule_kind, - span, - } = eat_ident(self.lexer, &Scope::new(), &Selector::new(), span_before)?; - if at_rule_kind.is_empty() { - return Err(("Expected identifier.", span).into()); - } - match AtRuleKind::from(at_rule_kind.as_str()) { + let rule = eat_ident(self.lexer, &Scope::new(), &Selector::new(), span_before)?; + match AtRuleKind::try_from(&rule)? { AtRuleKind::Include => rules.extend(eat_include( self.lexer, &Scope::new(), &Selector::new(), None, - span, + rule.span, )?), AtRuleKind::Import => { devour_whitespace(self.lexer); @@ -259,7 +254,7 @@ impl<'a> StyleSheetParser<'a> { )? .node .unquote() - .to_css_string(span)?, + .to_css_string(rule.span)?, ); } _ => return Err(("Expected string.", next.pos()).into()), @@ -282,7 +277,7 @@ impl<'a> StyleSheetParser<'a> { v => { let rule = AtRule::from_tokens( v, - span, + rule.span, self.lexer, &mut Scope::new(), &Selector::new(), diff --git a/tests/error.rs b/tests/error.rs index c6f8cdd..25c10ea 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -200,4 +200,11 @@ error!(value_after_style, "a {}a", "Error: expected \"{\"."); test!(whitespace_after_style, "a {}\t\n ", ""); test!(toplevel_semicolon, ";", ""); test!(toplevel_semicolon_after_style, "a {};", ""); -error!(nothing_after_hash_in_interpolated_ident_body, "a {color: foo#", "Error: Expected identifier."); +error!( + nothing_after_hash_in_interpolated_ident_body, + "a {color: foo#", "Error: Expected identifier." +); +error!( + at_else_alone, + "@else {}", "Error: This at-rule is not allowed here." +);