2020-01-04 22:55:04 -05:00
|
|
|
#![warn(
|
|
|
|
clippy::all,
|
|
|
|
clippy::restriction,
|
|
|
|
clippy::pedantic,
|
|
|
|
clippy::nursery,
|
|
|
|
// clippy::cargo
|
|
|
|
)]
|
|
|
|
#![deny(missing_debug_implementations)]
|
|
|
|
#![allow(
|
|
|
|
dead_code,
|
|
|
|
clippy::pub_enum_variant_names,
|
|
|
|
clippy::implicit_return,
|
|
|
|
clippy::use_self,
|
|
|
|
clippy::missing_docs_in_private_items,
|
|
|
|
clippy::todo,
|
|
|
|
clippy::unreachable,
|
|
|
|
clippy::wildcard_enum_match_arm,
|
|
|
|
clippy::option_expect_used,
|
|
|
|
clippy::panic,
|
|
|
|
clippy::unused_self,
|
|
|
|
clippy::too_many_lines,
|
|
|
|
clippy::integer_arithmetic,
|
2020-01-11 14:51:31 -05:00
|
|
|
clippy::missing_errors_doc,
|
2020-01-11 16:12:23 -05:00
|
|
|
clippy::let_underscore_must_use,
|
|
|
|
clippy::module_name_repetitions
|
2020-01-04 22:55:04 -05:00
|
|
|
)]
|
2020-01-12 10:54:46 -05:00
|
|
|
// todo! handle erroring on styles at the toplevel
|
2020-01-06 17:06:37 -05:00
|
|
|
use std::fmt::{self, Display};
|
2020-01-04 22:55:04 -05:00
|
|
|
use std::fs;
|
|
|
|
use std::io;
|
2020-01-06 17:06:37 -05:00
|
|
|
use std::iter::{Iterator, Peekable};
|
2020-01-07 19:58:38 -05:00
|
|
|
use std::path::Path;
|
2020-01-04 22:55:04 -05:00
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
use crate::common::{AtRule, Keyword, Op, Pos, Printer, Scope, Symbol, Whitespace};
|
2020-01-05 12:45:51 -05:00
|
|
|
use crate::css::Css;
|
2020-01-06 18:55:51 -05:00
|
|
|
use crate::error::SassError;
|
2020-01-04 22:55:04 -05:00
|
|
|
use crate::format::PrettyPrinter;
|
2020-01-17 16:23:21 -05:00
|
|
|
use crate::function::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
|
2020-01-04 22:55:04 -05:00
|
|
|
use crate::lexer::Lexer;
|
2020-01-17 16:23:21 -05:00
|
|
|
use crate::mixin::Mixin;
|
2020-01-11 14:51:31 -05:00
|
|
|
use crate::selector::{Attribute, Selector};
|
2020-01-06 16:50:16 -05:00
|
|
|
use crate::style::Style;
|
2020-01-04 22:55:04 -05:00
|
|
|
use crate::units::Unit;
|
2020-01-14 19:34:13 -05:00
|
|
|
use crate::utils::{devour_whitespace, eat_variable_value, IsWhitespace};
|
2020-01-04 22:55:04 -05:00
|
|
|
|
|
|
|
mod color;
|
|
|
|
mod common;
|
2020-01-05 12:45:51 -05:00
|
|
|
mod css;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod error;
|
|
|
|
mod format;
|
2020-01-17 16:23:21 -05:00
|
|
|
mod function;
|
2020-01-11 18:42:42 -05:00
|
|
|
mod imports;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod lexer;
|
2020-01-12 17:44:49 -05:00
|
|
|
mod mixin;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod selector;
|
2020-01-06 16:50:16 -05:00
|
|
|
mod style;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod units;
|
2020-01-12 19:56:58 -05:00
|
|
|
mod utils;
|
2020-01-04 22:55:04 -05:00
|
|
|
|
2020-01-06 18:55:51 -05:00
|
|
|
type SassResult<T> = Result<T, SassError>;
|
|
|
|
|
2020-01-08 20:39:05 -05:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct Token {
|
|
|
|
pos: Pos,
|
|
|
|
pub kind: TokenKind,
|
|
|
|
}
|
|
|
|
|
2020-01-12 20:15:27 -05:00
|
|
|
impl IsWhitespace for Token {
|
|
|
|
fn is_whitespace(&self) -> bool {
|
|
|
|
if let TokenKind::Whitespace(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IsWhitespace for &Token {
|
|
|
|
fn is_whitespace(&self) -> bool {
|
|
|
|
if let TokenKind::Whitespace(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub enum TokenKind {
|
|
|
|
Ident(String),
|
|
|
|
Symbol(Symbol),
|
2020-01-07 18:37:28 -05:00
|
|
|
AtRule(AtRule),
|
2020-01-04 22:55:04 -05:00
|
|
|
Keyword(Keyword),
|
|
|
|
Number(String),
|
|
|
|
Unit(Unit),
|
|
|
|
Whitespace(Whitespace),
|
|
|
|
Variable(String),
|
2020-01-11 14:51:31 -05:00
|
|
|
Attribute(Attribute),
|
2020-01-06 00:40:29 -05:00
|
|
|
Op(Op),
|
2020-01-05 19:21:58 -05:00
|
|
|
MultilineComment(String),
|
2020-01-12 10:54:46 -05:00
|
|
|
Interpolation,
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for TokenKind {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2020-01-07 18:37:28 -05:00
|
|
|
TokenKind::Ident(s) | TokenKind::Number(s) => write!(f, "{}", s),
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Symbol(s) => write!(f, "{}", s),
|
2020-01-07 18:37:28 -05:00
|
|
|
TokenKind::AtRule(s) => write!(f, "{}", s),
|
2020-01-06 00:40:29 -05:00
|
|
|
TokenKind::Op(s) => write!(f, "{}", s),
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Unit(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Whitespace(s) => write!(f, "{}", s),
|
2020-01-11 14:51:31 -05:00
|
|
|
TokenKind::Attribute(s) => write!(f, "{}", s),
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
|
|
|
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
|
|
|
TokenKind::Variable(s) => write!(f, "${}", s),
|
2020-01-12 10:54:46 -05:00
|
|
|
TokenKind::Interpolation => {
|
|
|
|
panic!("we don't want to format TokenKind::Interpolation using Display")
|
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Represents a parsed SASS stylesheet with nesting
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
|
|
pub struct StyleSheet {
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub enum Stmt {
|
2020-01-09 20:56:09 -05:00
|
|
|
/// A [`Style`](/grass/style/struct.Style)
|
2020-01-04 22:55:04 -05:00
|
|
|
Style(Style),
|
2020-01-09 20:56:09 -05:00
|
|
|
/// A [`RuleSet`](/grass/struct.RuleSet.html)
|
2020-01-04 22:55:04 -05:00
|
|
|
RuleSet(RuleSet),
|
2020-01-08 20:58:02 -05:00
|
|
|
/// A multiline comment: `/* foo bar */`
|
2020-01-08 20:39:05 -05:00
|
|
|
MultilineComment(String),
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Represents a single rule set. Rule sets can contain other rule sets
|
2020-01-11 14:51:31 -05:00
|
|
|
///
|
2020-01-12 17:44:49 -05:00
|
|
|
/// ```scss
|
2020-01-08 20:58:02 -05:00
|
|
|
/// a {
|
|
|
|
/// color: blue;
|
|
|
|
/// b {
|
|
|
|
/// color: red;
|
|
|
|
/// }
|
|
|
|
/// }
|
2020-01-12 17:44:49 -05:00
|
|
|
/// ```
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct RuleSet {
|
|
|
|
selector: Selector,
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
// potential optimization: we don't *need* to own the selector
|
2020-01-05 12:37:50 -05:00
|
|
|
super_selector: Selector,
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
/// An intermediate representation of what are essentially single lines
|
|
|
|
/// todo! rename this
|
2020-01-17 08:14:10 -05:00
|
|
|
#[derive(Clone, Debug)]
|
2020-01-04 22:55:04 -05:00
|
|
|
enum Expr {
|
2020-01-08 20:58:02 -05:00
|
|
|
/// A style: `color: red`
|
2020-01-04 22:55:04 -05:00
|
|
|
Style(Style),
|
2020-01-12 19:56:58 -05:00
|
|
|
/// A collection of styles, from a mixin or function
|
2020-01-17 08:14:10 -05:00
|
|
|
// Styles(Vec<Style>),
|
2020-01-08 20:58:02 -05:00
|
|
|
/// A full selector `a > h1`
|
2020-01-04 22:55:04 -05:00
|
|
|
Selector(Selector),
|
2020-01-08 20:58:02 -05:00
|
|
|
/// A variable declaration `$var: 1px`
|
2020-01-05 19:21:58 -05:00
|
|
|
VariableDecl(String, Vec<Token>),
|
2020-01-17 08:14:10 -05:00
|
|
|
/// A mixin declaration `@mixin foo {}`
|
|
|
|
MixinDecl(String, Mixin),
|
|
|
|
/// An include statement `@include foo;`
|
|
|
|
Include(Vec<Stmt>),
|
2020-01-08 20:58:02 -05:00
|
|
|
/// A multiline comment: `/* foobar */`
|
2020-01-08 20:39:05 -05:00
|
|
|
MultilineComment(String),
|
2020-01-12 10:54:46 -05:00
|
|
|
// /// Function call: `calc(10vw - 1px)`
|
|
|
|
// FuncCall(String, Vec<Token>),
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl StyleSheet {
|
2020-01-06 18:55:51 -05:00
|
|
|
pub fn new(input: &str) -> SassResult<StyleSheet> {
|
2020-01-04 22:55:04 -05:00
|
|
|
StyleSheetParser {
|
2020-01-12 17:44:49 -05:00
|
|
|
global_scope: Scope::new(),
|
2020-01-04 22:55:04 -05:00
|
|
|
lexer: Lexer::new(input).peekable(),
|
|
|
|
rules: Vec::new(),
|
2020-01-06 18:55:51 -05:00
|
|
|
scope: 0,
|
2020-01-07 19:24:37 -05:00
|
|
|
file: String::from("stdin"),
|
|
|
|
}
|
|
|
|
.parse_toplevel()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_path<P: AsRef<Path> + Into<String>>(p: P) -> SassResult<StyleSheet> {
|
|
|
|
StyleSheetParser {
|
2020-01-12 17:44:49 -05:00
|
|
|
global_scope: Scope::new(),
|
2020-01-07 19:24:37 -05:00
|
|
|
lexer: Lexer::new(&fs::read_to_string(p.as_ref())?).peekable(),
|
|
|
|
rules: Vec::new(),
|
|
|
|
scope: 0,
|
|
|
|
file: p.into(),
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
.parse_toplevel()
|
|
|
|
}
|
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Print the internal representation of a parsed stylesheet
|
2020-01-11 14:51:31 -05:00
|
|
|
///
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Very closely resembles the origin SASS, but contains only things translatable
|
|
|
|
/// to pure CSS
|
2020-01-11 14:51:31 -05:00
|
|
|
///
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Used mainly in debugging, but can at times be useful
|
2020-01-04 22:55:04 -05:00
|
|
|
pub fn pretty_print<W: std::io::Write>(&self, buf: W) -> io::Result<()> {
|
|
|
|
PrettyPrinter::new(buf).pretty_print(self)
|
|
|
|
}
|
2020-01-05 12:37:02 -05:00
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
fn pretty_print_selectors<W: std::io::Write>(&self, buf: W) -> io::Result<()> {
|
2020-01-05 12:37:02 -05:00
|
|
|
PrettyPrinter::new(buf).pretty_print_preserve_super_selectors(self)
|
|
|
|
}
|
|
|
|
|
2020-01-08 20:58:02 -05:00
|
|
|
/// Write the internal representation as CSS to `buf`
|
2020-01-05 12:37:02 -05:00
|
|
|
pub fn print_as_css<W: std::io::Write>(self, buf: &mut W) -> io::Result<()> {
|
|
|
|
Css::from_stylesheet(self).pretty_print(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct StyleSheetParser<'a> {
|
2020-01-12 17:44:49 -05:00
|
|
|
global_scope: Scope,
|
2020-01-04 22:55:04 -05:00
|
|
|
lexer: Peekable<Lexer<'a>>,
|
|
|
|
rules: Vec<Stmt>,
|
2020-01-06 18:55:51 -05:00
|
|
|
scope: u32,
|
2020-01-07 19:24:37 -05:00
|
|
|
file: String,
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> StyleSheetParser<'a> {
|
2020-01-07 19:24:37 -05:00
|
|
|
fn parse_toplevel(mut self) -> SassResult<StyleSheet> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let mut rules = Vec::new();
|
2020-01-07 18:37:28 -05:00
|
|
|
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
|
|
|
match kind.clone() {
|
2020-01-04 22:55:04 -05:00
|
|
|
TokenKind::Ident(_)
|
2020-01-11 14:51:31 -05:00
|
|
|
| TokenKind::Attribute(_)
|
2020-01-12 10:54:46 -05:00
|
|
|
| TokenKind::Interpolation
|
2020-01-04 22:55:04 -05:00
|
|
|
| TokenKind::Symbol(Symbol::Hash)
|
|
|
|
| TokenKind::Symbol(Symbol::Colon)
|
|
|
|
| TokenKind::Symbol(Symbol::Mul)
|
2020-01-12 19:56:58 -05:00
|
|
|
| TokenKind::Symbol(Symbol::Period) => rules
|
|
|
|
.extend(self.eat_rules(&Selector(Vec::new()), &mut self.global_scope.clone())),
|
2020-01-11 20:41:36 -05:00
|
|
|
TokenKind::Whitespace(_) => {
|
2020-01-04 22:55:04 -05:00
|
|
|
self.lexer.next();
|
|
|
|
continue;
|
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Variable(name) => {
|
2020-01-11 19:39:42 -05:00
|
|
|
let Token { pos, .. } = self
|
2020-01-07 18:37:28 -05:00
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("this cannot occur as we have already peeked");
|
2020-01-12 20:15:27 -05:00
|
|
|
devour_whitespace(&mut self.lexer);
|
2020-01-07 19:58:38 -05:00
|
|
|
if self
|
2020-01-05 19:21:58 -05:00
|
|
|
.lexer
|
|
|
|
.next()
|
2020-01-07 18:37:28 -05:00
|
|
|
.unwrap_or_else(|| self.error(pos, "expected value after variable"))
|
2020-01-05 19:21:58 -05:00
|
|
|
.kind
|
|
|
|
!= TokenKind::Symbol(Symbol::Colon)
|
|
|
|
{
|
2020-01-07 18:37:28 -05:00
|
|
|
self.error(pos, "unexpected variable use at toplevel");
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
2020-01-14 19:34:13 -05:00
|
|
|
let val = eat_variable_value(&mut self.lexer, &self.global_scope)
|
|
|
|
.unwrap_or_else(|err| self.error(err.0, err.1));
|
2020-01-12 17:44:49 -05:00
|
|
|
self.global_scope.vars.insert(name, val);
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
2020-01-07 19:58:38 -05:00
|
|
|
TokenKind::MultilineComment(comment) => {
|
2020-01-08 20:39:05 -05:00
|
|
|
self.lexer.next();
|
|
|
|
rules.push(Stmt::MultilineComment(comment));
|
2020-01-07 19:58:38 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
TokenKind::AtRule(AtRule::Mixin) => {
|
2020-01-17 10:44:16 -05:00
|
|
|
let (name, mixin) =
|
|
|
|
parse_mixin(&mut self.lexer, self.global_scope.clone()).unwrap();
|
2020-01-17 08:14:10 -05:00
|
|
|
self.global_scope.mixins.insert(name, mixin);
|
|
|
|
}
|
2020-01-12 20:15:27 -05:00
|
|
|
TokenKind::AtRule(_) => {
|
2020-01-17 08:14:10 -05:00
|
|
|
if let Some(Token {
|
2020-01-17 21:20:56 -05:00
|
|
|
kind: TokenKind::AtRule(ref rule),
|
2020-01-17 08:14:10 -05:00
|
|
|
pos,
|
|
|
|
}) = self.lexer.next()
|
|
|
|
{
|
2020-01-17 21:20:56 -05:00
|
|
|
match eat_at_rule(rule, pos, &mut self.lexer, &self.global_scope) {
|
2020-01-17 10:44:16 -05:00
|
|
|
Ok(_) => todo!(),
|
2020-01-17 08:14:10 -05:00
|
|
|
Err(Printer::Error(pos, message)) => self.error(pos, &message),
|
|
|
|
Err(Printer::Warn(pos, message)) => self.warn(pos, &message),
|
|
|
|
Err(Printer::Debug(pos, message)) => self.debug(pos, &message),
|
|
|
|
}
|
|
|
|
}
|
2020-01-12 20:15:27 -05:00
|
|
|
}
|
2020-01-11 20:41:36 -05:00
|
|
|
_ => {
|
2020-01-17 10:44:16 -05:00
|
|
|
if let Some(Token { pos, .. }) = self.lexer.next() {
|
|
|
|
self.error(pos, "unexpected toplevel token")
|
2020-01-11 20:41:36 -05:00
|
|
|
} else {
|
|
|
|
unsafe { std::hint::unreachable_unchecked() }
|
|
|
|
}
|
2020-01-12 10:54:46 -05:00
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
};
|
|
|
|
}
|
2020-01-06 19:23:27 -05:00
|
|
|
Ok(StyleSheet { rules })
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
2020-01-12 19:56:58 -05:00
|
|
|
fn eat_rules(&mut self, super_selector: &Selector, scope: &mut Scope) -> Vec<Stmt> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let mut stmts = Vec::new();
|
2020-01-17 08:14:10 -05:00
|
|
|
while let Ok(tok) = eat_expr(&mut self.lexer, scope, super_selector) {
|
2020-01-04 22:55:04 -05:00
|
|
|
match tok {
|
|
|
|
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
2020-01-17 08:14:10 -05:00
|
|
|
Expr::MixinDecl(name, mixin) => {
|
|
|
|
scope.mixins.insert(name, mixin);
|
|
|
|
}
|
|
|
|
Expr::Include(rules) => {
|
|
|
|
stmts.extend(rules);
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
Expr::Selector(s) => {
|
2020-01-06 19:23:27 -05:00
|
|
|
self.scope += 1;
|
2020-01-12 17:44:49 -05:00
|
|
|
let rules = self.eat_rules(&super_selector.clone().zip(s.clone()), scope);
|
2020-01-04 22:55:04 -05:00
|
|
|
stmts.push(Stmt::RuleSet(RuleSet {
|
|
|
|
super_selector: super_selector.clone(),
|
|
|
|
selector: s,
|
|
|
|
rules,
|
|
|
|
}));
|
2020-01-06 19:23:27 -05:00
|
|
|
self.scope -= 1;
|
2020-01-07 18:37:28 -05:00
|
|
|
if self.scope == 0 {
|
|
|
|
return stmts;
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
Expr::VariableDecl(name, val) => {
|
2020-01-06 19:23:27 -05:00
|
|
|
if self.scope == 0 {
|
2020-01-12 17:44:49 -05:00
|
|
|
scope.vars.insert(name.clone(), val.clone());
|
|
|
|
self.global_scope.vars.insert(name, val);
|
2020-01-06 19:23:27 -05:00
|
|
|
} else {
|
2020-01-12 17:44:49 -05:00
|
|
|
scope.vars.insert(name, val);
|
2020-01-06 19:23:27 -05:00
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
2020-01-08 20:39:05 -05:00
|
|
|
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)),
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
stmts
|
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
fn eat_include<I: Iterator<Item = Token>>(
|
2020-01-17 10:44:16 -05:00
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: &Scope,
|
|
|
|
super_selector: &Selector,
|
|
|
|
) -> Result<Vec<Stmt>, (Pos, &'static str)> {
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let Token { kind, pos } = toks.next().unwrap();
|
|
|
|
let name = if let TokenKind::Ident(s) = kind {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
return Err((pos, "expected identifier"));
|
|
|
|
};
|
2020-01-17 08:14:10 -05:00
|
|
|
|
2020-01-17 10:44:16 -05:00
|
|
|
devour_whitespace(toks);
|
2020-01-17 08:14:10 -05:00
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
let args = match toks.next() {
|
2020-01-17 10:44:16 -05:00
|
|
|
Some(Token {
|
|
|
|
kind: TokenKind::Symbol(Symbol::SemiColon),
|
|
|
|
..
|
2020-01-17 14:44:55 -05:00
|
|
|
}) => CallArgs::new(),
|
2020-01-17 10:44:16 -05:00
|
|
|
Some(Token {
|
|
|
|
kind: TokenKind::Symbol(Symbol::OpenParen),
|
|
|
|
..
|
2020-01-17 14:44:55 -05:00
|
|
|
}) => eat_call_args(toks),
|
2020-01-17 10:44:16 -05:00
|
|
|
Some(Token { pos, .. }) => return Err((pos, "expected `(` or `;`")),
|
|
|
|
None => return Err((pos, "unexpected EOF")),
|
2020-01-17 14:44:55 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
devour_whitespace(toks);
|
|
|
|
|
|
|
|
if !args.is_empty() {
|
|
|
|
if let Some(tok) = toks.next() {
|
|
|
|
assert_eq!(tok.kind, TokenKind::Symbol(Symbol::SemiColon));
|
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
devour_whitespace(toks);
|
|
|
|
|
2020-01-17 10:44:16 -05:00
|
|
|
let mut mixin = if let Some(m) = scope.mixins.get(&name) {
|
|
|
|
m.clone()
|
|
|
|
} else {
|
|
|
|
return Err((pos, "expected identifier"));
|
|
|
|
};
|
2020-01-17 16:23:21 -05:00
|
|
|
let rules = mixin
|
|
|
|
.call_with_args(&args)
|
|
|
|
.eval(super_selector, &mut scope.clone());
|
2020-01-17 10:44:16 -05:00
|
|
|
Ok(rules)
|
|
|
|
}
|
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
fn parse_mixin<I: Iterator<Item = Token>>(
|
2020-01-17 08:14:10 -05:00
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: Scope,
|
|
|
|
) -> Result<(String, Mixin), Printer> {
|
|
|
|
let Token { pos, .. } = toks.next().unwrap();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let name = if let Some(Token {
|
|
|
|
kind: TokenKind::Ident(s),
|
|
|
|
..
|
|
|
|
}) = toks.next()
|
|
|
|
{
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
return Err(Printer::Error(
|
|
|
|
pos,
|
|
|
|
String::from("expected identifier after mixin declaration"),
|
|
|
|
));
|
|
|
|
};
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let args = match toks.next() {
|
|
|
|
Some(Token {
|
|
|
|
kind: TokenKind::Symbol(Symbol::OpenParen),
|
|
|
|
..
|
2020-01-17 14:44:55 -05:00
|
|
|
}) => eat_func_args(toks),
|
2020-01-17 08:14:10 -05:00
|
|
|
Some(Token {
|
|
|
|
kind: TokenKind::Symbol(Symbol::OpenCurlyBrace),
|
|
|
|
..
|
|
|
|
}) => FuncArgs::new(),
|
|
|
|
_ => return Err(Printer::Error(pos, String::from("expected `(` or `{`"))),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut nesting = 1;
|
|
|
|
let mut body = Vec::new();
|
|
|
|
|
|
|
|
while nesting > 0 {
|
|
|
|
if let Some(tok) = toks.next() {
|
2020-01-11 20:41:36 -05:00
|
|
|
match &tok.kind {
|
2020-01-17 08:14:10 -05:00
|
|
|
TokenKind::Symbol(Symbol::OpenCurlyBrace) => nesting += 1,
|
|
|
|
TokenKind::Symbol(Symbol::CloseCurlyBrace) => nesting -= 1,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
body.push(tok)
|
|
|
|
} else {
|
|
|
|
todo!("unexpected EOF")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((name, Mixin::new(scope, args, body)))
|
|
|
|
}
|
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
fn eat_at_rule<I: Iterator<Item = Token>>(
|
2020-01-17 21:20:56 -05:00
|
|
|
rule: &AtRule,
|
2020-01-17 08:14:10 -05:00
|
|
|
pos: Pos,
|
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: &Scope,
|
|
|
|
) -> Result<Expr, Printer> {
|
|
|
|
match rule {
|
|
|
|
AtRule::Error => {
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let message = toks
|
|
|
|
.take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon))
|
|
|
|
.map(|x| x.kind.to_string())
|
|
|
|
.collect::<String>();
|
|
|
|
Err(Printer::Error(pos, message))
|
|
|
|
}
|
|
|
|
AtRule::Warn => {
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let message = toks
|
|
|
|
.take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon))
|
|
|
|
.map(|x| x.kind.to_string())
|
|
|
|
.collect::<String>();
|
|
|
|
Err(Printer::Warn(pos, message))
|
|
|
|
}
|
|
|
|
AtRule::Debug => {
|
|
|
|
devour_whitespace(toks);
|
|
|
|
let message = toks
|
|
|
|
.by_ref()
|
|
|
|
.take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon))
|
|
|
|
.map(|x| x.kind.to_string())
|
|
|
|
.collect::<String>();
|
|
|
|
Err(Printer::Debug(pos, message))
|
|
|
|
}
|
|
|
|
AtRule::Mixin => {
|
|
|
|
let (name, mixin) = parse_mixin(toks, scope.clone())?;
|
|
|
|
Ok(Expr::MixinDecl(name, mixin))
|
|
|
|
}
|
|
|
|
// AtRule::Include => return Some(self.eat_include()),
|
|
|
|
_ => todo!("encountered unimplemented at rule"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 14:44:55 -05:00
|
|
|
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
2020-01-17 08:14:10 -05:00
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: &Scope,
|
|
|
|
super_selector: &Selector,
|
|
|
|
) -> Result<Expr, ()> {
|
|
|
|
let mut values = Vec::with_capacity(5);
|
|
|
|
while let Some(tok) = toks.peek() {
|
|
|
|
match &tok.kind {
|
|
|
|
TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
return Ok(Expr::Style(Style::from_tokens(&values, scope)?));
|
|
|
|
}
|
|
|
|
TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
return Ok(Expr::Selector(Selector::from_tokens(
|
|
|
|
&mut values.iter().peekable(),
|
|
|
|
super_selector,
|
|
|
|
scope,
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
TokenKind::Variable(_) => {
|
|
|
|
let tok = toks.next().unwrap();
|
|
|
|
let name = if let TokenKind::Variable(n) = tok.kind {
|
|
|
|
n
|
|
|
|
} else {
|
|
|
|
unsafe { std::hint::unreachable_unchecked() }
|
|
|
|
};
|
|
|
|
if let TokenKind::Symbol(Symbol::Colon) =
|
|
|
|
toks.peek().expect("expected something after variable").kind
|
|
|
|
{
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
return Ok(Expr::VariableDecl(
|
|
|
|
name,
|
|
|
|
eat_variable_value(toks, scope).unwrap(), // .unwrap_or_else(|err| self.error(err.0, err.1)),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
values.push(Token {
|
|
|
|
kind: TokenKind::Variable(name),
|
|
|
|
pos: tok.pos,
|
|
|
|
});
|
2020-01-05 19:21:58 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
TokenKind::MultilineComment(_) => {
|
|
|
|
let tok = toks.next().unwrap();
|
|
|
|
let s = if let TokenKind::MultilineComment(s) = &tok.kind {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
unsafe { std::hint::unreachable_unchecked() }
|
|
|
|
};
|
|
|
|
devour_whitespace(toks);
|
|
|
|
if values.is_empty() {
|
|
|
|
return Ok(Expr::MultilineComment(s.clone()));
|
|
|
|
} else {
|
|
|
|
values.push(tok.clone())
|
2020-01-08 20:39:05 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
TokenKind::AtRule(AtRule::Include) => {
|
2020-01-17 10:44:16 -05:00
|
|
|
return Ok(Expr::Include(
|
|
|
|
eat_include(toks, scope, super_selector).unwrap(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
TokenKind::AtRule(AtRule::Mixin) => {
|
|
|
|
toks.next();
|
|
|
|
let (name, mixin) = parse_mixin(toks, scope.clone()).unwrap();
|
|
|
|
return Ok(Expr::MixinDecl(name, mixin));
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
TokenKind::AtRule(_) => {
|
|
|
|
if let Some(Token {
|
2020-01-17 21:20:56 -05:00
|
|
|
kind: TokenKind::AtRule(ref rule),
|
2020-01-17 08:14:10 -05:00
|
|
|
pos,
|
|
|
|
}) = toks.next()
|
|
|
|
{
|
|
|
|
if let Ok(a) = eat_at_rule(rule, pos, toks, scope) {
|
2020-01-17 10:44:16 -05:00
|
|
|
return Ok(a);
|
2020-01-12 19:56:58 -05:00
|
|
|
}
|
2020-01-12 20:15:27 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
TokenKind::Interpolation => {
|
|
|
|
while let Some(tok) = toks.next() {
|
|
|
|
if tok.kind == TokenKind::Symbol(Symbol::CloseCurlyBrace) {
|
2020-01-12 10:54:46 -05:00
|
|
|
values.push(tok);
|
2020-01-17 08:14:10 -05:00
|
|
|
break;
|
2020-01-12 10:54:46 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
values.push(tok);
|
2020-01-12 10:54:46 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
if let Some(tok) = toks.next() {
|
2020-01-17 14:44:55 -05:00
|
|
|
values.push(tok)
|
2020-01-17 08:14:10 -05:00
|
|
|
} else {
|
2020-01-17 10:44:16 -05:00
|
|
|
unsafe { std::hint::unreachable_unchecked() }
|
2020-01-11 20:41:36 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
}
|
|
|
|
};
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
2020-01-17 08:14:10 -05:00
|
|
|
Err(())
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
2020-01-11 20:41:36 -05:00
|
|
|
/// Functions that print to stdout or stderr
|
|
|
|
impl<'a> StyleSheetParser<'a> {
|
|
|
|
fn debug(&self, pos: Pos, message: &str) {
|
|
|
|
println!("{}:{} Debug: {}", self.file, pos.line(), message);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn warn(&self, pos: Pos, message: &str) {
|
|
|
|
eprintln!(
|
|
|
|
"Warning: {}\n\t{} {}:{} todo!(scope)",
|
|
|
|
message,
|
|
|
|
self.file,
|
|
|
|
pos.line(),
|
|
|
|
pos.column()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn error(&self, pos: Pos, message: &str) -> ! {
|
|
|
|
eprintln!("Error: {}", message);
|
|
|
|
eprintln!(
|
|
|
|
"{} {}:{} todo!(scope) on line {} at column {}",
|
|
|
|
self.file,
|
|
|
|
pos.line(),
|
|
|
|
pos.column(),
|
|
|
|
pos.line(),
|
|
|
|
pos.column()
|
|
|
|
);
|
|
|
|
let padding = vec![' '; format!("{}", pos.line()).len() + 1]
|
|
|
|
.iter()
|
|
|
|
.collect::<String>();
|
|
|
|
eprintln!("{}|", padding);
|
|
|
|
eprint!("{} | ", pos.line());
|
|
|
|
eprintln!("todo! get line to print as error");
|
|
|
|
eprintln!(
|
|
|
|
"{}| {}^",
|
|
|
|
padding,
|
|
|
|
vec![' '; pos.column() as usize].iter().collect::<String>()
|
|
|
|
);
|
|
|
|
eprintln!("{}|", padding);
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 18:55:51 -05:00
|
|
|
fn main() -> SassResult<()> {
|
2020-01-12 17:44:49 -05:00
|
|
|
let mut stdout = std::io::BufWriter::new(std::io::stdout());
|
2020-01-07 19:24:37 -05:00
|
|
|
let s = StyleSheet::from_path("input.scss")?;
|
2020-01-04 22:55:04 -05:00
|
|
|
// dbg!(s);
|
2020-01-17 16:14:19 -05:00
|
|
|
// s.pretty_print(&mut stdout)?;
|
2020-01-05 12:52:50 -05:00
|
|
|
// s.pretty_print_selectors(&mut stdout)?;
|
2020-01-17 16:14:19 -05:00
|
|
|
s.print_as_css(&mut stdout)?;
|
2020-01-05 12:52:50 -05:00
|
|
|
// dbg!(Css::from_stylesheet(s));
|
2020-01-04 22:55:04 -05:00
|
|
|
// println!("{}", s);
|
|
|
|
// drop(input);
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-01-05 20:26:34 -05:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2020-01-14 20:23:05 -05:00
|
|
|
macro_rules! test {
|
|
|
|
($func:ident, $input:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
StyleSheet::new($input)
|
|
|
|
.expect(concat!("failed to parse on ", $input))
|
|
|
|
.print_as_css(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($input),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
($func:ident, $input:literal, $output:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
StyleSheet::new($input)
|
|
|
|
.expect(concat!("failed to parse on ", $input))
|
|
|
|
.print_as_css(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($output),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod css_variables {
|
2020-01-05 20:26:34 -05:00
|
|
|
use super::StyleSheet;
|
2020-01-14 20:23:05 -05:00
|
|
|
test!(
|
|
|
|
basic_variable,
|
|
|
|
"$height: 1px;\na {\n height: $height;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_redeclaration,
|
|
|
|
"$a: 1px;\n$a: 2px;\na {\n height: $a;\n}\n",
|
|
|
|
"a {\n height: 2px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_shadowing,
|
|
|
|
"$a: 1px;\n$b: $a;\na {\n height: $b;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_shadowing_val_does_not_change,
|
|
|
|
"$a: 1px;\n$b: $a; $a: 2px;\na {\n height: $b;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_shadowing_val_does_not_change_complex,
|
|
|
|
"a {\n color: red;\n}\n$y: before;\n$x: 1 2 $y;\n$y: after;\nfoo {\n a: $x;\n}",
|
|
|
|
"a {\n color: red;\n}\nfoo {\n a: 1 2 before;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_whitespace,
|
|
|
|
"$a : 1px ;\na {\n height: $a;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_after_variable,
|
|
|
|
"$a: 1px;\na {\n height: $a;\n color: red;\n}\n",
|
|
|
|
"a {\n height: 1px;\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
literal_and_variable_as_val,
|
|
|
|
"$a: 1px;\na {\n height: 1 $a;\n}\n",
|
|
|
|
"a {\n height: 1 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
literal_and_variable_as_var,
|
|
|
|
"$a: 1px;\n$b: 1 $a;\na {\n height: $b;\n}\n",
|
|
|
|
"a {\n height: 1 1px;\n}\n"
|
|
|
|
);
|
2020-01-17 21:03:01 -05:00
|
|
|
test!(
|
|
|
|
eats_whitespace_after_variable_value,
|
|
|
|
"a {\n b {\n $c: red;\n }\n color: red;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-17 21:15:38 -05:00
|
|
|
test!(
|
|
|
|
variable_changes_through_new_ruleset,
|
|
|
|
"a {\n $c: red;\nb {\n $c: blue;\n }\n color: $c;\n}\n",
|
|
|
|
"a {\n color: blue;\n}\n"
|
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
}
|
2020-01-05 20:26:34 -05:00
|
|
|
|
2020-01-14 20:23:05 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod css_selectors {
|
|
|
|
use super::StyleSheet;
|
2020-01-05 20:51:14 -05:00
|
|
|
test!(
|
2020-01-11 14:51:31 -05:00
|
|
|
selector_nesting_el_mul_el,
|
2020-01-05 20:51:14 -05:00
|
|
|
"a, b {\n a, b {\n color: red\n}\n}\n",
|
2020-01-11 14:51:31 -05:00
|
|
|
"a a, a b, b a, b b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_element, "a {\n color: red;\n}\n");
|
|
|
|
test!(selector_id, "#id {\n color: red;\n}\n");
|
|
|
|
test!(selector_class, ".class {\n color: red;\n}\n");
|
|
|
|
test!(selector_el_descendant, "a a {\n color: red;\n}\n");
|
|
|
|
test!(selector_universal, "* {\n color: red;\n}\n");
|
|
|
|
test!(selector_el_class_and, "a.class {\n color: red;\n}\n");
|
|
|
|
test!(selector_el_id_and, "a#class {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_el_class_descendant,
|
|
|
|
"a .class {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_el_id_descendant, "a #class {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_el_universal_descendant,
|
|
|
|
"a * {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_universal_el_descendant,
|
|
|
|
"* a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
|
|
|
|
test!(selector_attribute_any, "[attr] {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_attribute_equals,
|
|
|
|
"[attr=val] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_attribute_single_quotes,
|
|
|
|
"[attr='val'] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_attribute_double_quotes,
|
|
|
|
"[attr=\"val\"] {\n color: red;\n}\n"
|
2020-01-05 20:51:14 -05:00
|
|
|
);
|
2020-01-11 14:51:31 -05:00
|
|
|
test!(selector_attribute_in, "[attr~=val] {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_attribute_begins_hyphen_or_exact,
|
|
|
|
"[attr|=val] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_attribute_starts_with,
|
|
|
|
"[attr^=val] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_attribute_ends_with,
|
|
|
|
"[attr$=val] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_attribute_contains,
|
|
|
|
"[attr*=val] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_el_attribute_and, "a[attr] {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_el_attribute_descendant,
|
|
|
|
"a [attr] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_el_mul_el, "a, b {\n color: red;\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_el_immediate_child_el,
|
|
|
|
"a > b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_el_following_el, "a + b {\n color: red;\n}\n");
|
|
|
|
test!(selector_el_preceding_el, "a ~ b {\n color: red;\n}\n");
|
|
|
|
test!(selector_pseudo, ":pseudo {\n color: red;\n}\n");
|
2020-01-12 17:44:49 -05:00
|
|
|
test!(selector_el_and_pseudo, "a:pseudo {\n color: red;\n}\n");
|
2020-01-11 14:51:31 -05:00
|
|
|
test!(
|
|
|
|
selector_el_pseudo_descendant,
|
|
|
|
"a :pseudo {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_pseudo_el_descendant,
|
|
|
|
":pseudo a {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-11 19:16:59 -05:00
|
|
|
test!(
|
|
|
|
selector_pseudo_paren_comma,
|
|
|
|
":pseudo(a, b, c) {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_pseudo_paren_space,
|
|
|
|
":pseudo(a b c) {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_el_pseudo_paren_and,
|
|
|
|
"a:pseudo(a, b, c) {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_el_pseudo_paren_descendant,
|
|
|
|
"a :pseudo(a, b, c) {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_pseudo_paren_el_descendant,
|
|
|
|
":pseudo(a, b, c) a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_pseudo_paren_el_nested,
|
|
|
|
"a {\n :pseudo(a, b, c) {\n color: red;\n }\n}\n",
|
|
|
|
"a :pseudo(a, b, c) {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
test!(selector_mul, "a, b {\n color: red;\n}\n");
|
2020-01-09 21:30:21 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
outer_ampersand,
|
|
|
|
"a, b {\n& c {\n color: red;\n}\n}\n",
|
|
|
|
"a c, b c {\n color: red;\n}\n"
|
2020-01-09 21:30:21 -05:00
|
|
|
);
|
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
inner_ampersand,
|
|
|
|
"a, b {\na & c {\n color: red;\n}\n}\n",
|
|
|
|
"a a c, a b c {\n color: red;\n}\n"
|
2020-01-09 21:30:21 -05:00
|
|
|
);
|
2020-01-14 17:39:19 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
ampersand_multiple_whitespace,
|
|
|
|
" a , b {\n&c {\n color: red;\n}\n}\n",
|
|
|
|
"ac, bc {\n color: red;\n}\n"
|
2020-01-14 17:39:19 -05:00
|
|
|
);
|
2020-01-08 20:39:05 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
ampersand_alone,
|
|
|
|
"a, b {\n& {\n color: red;\n}\n}\n",
|
|
|
|
"a, b {\n color: red;\n}\n"
|
2020-01-08 20:39:05 -05:00
|
|
|
);
|
2020-01-11 17:24:50 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
bem_dash_dash_selector,
|
|
|
|
"a {\n&--b {\n color: red;\n}\n}\n",
|
|
|
|
"a--b {\n color: red;\n}\n"
|
2020-01-11 17:24:50 -05:00
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
// test!(
|
|
|
|
// bem_underscore_selector,
|
|
|
|
// "a {\n&__b {\n color: red;\n}\n}\n",
|
|
|
|
// "a__b {\n color: red;\n}\n"
|
|
|
|
// );
|
|
|
|
test!(
|
|
|
|
selector_interpolation_start,
|
|
|
|
"#{a}bc {\n color: red;\n}\n",
|
|
|
|
"abc {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_interpolation_middle,
|
|
|
|
"a#{b}c {\n color: red;\n}\n",
|
|
|
|
"abc {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_interpolation_end,
|
|
|
|
"ab#{c} {\n color: red;\n}\n",
|
|
|
|
"abc {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_interpolation_variable,
|
|
|
|
"$a: foo;\nab#{$a} {\n color: red;\n}\n",
|
|
|
|
"abfoo {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
selector_whitespace,
|
|
|
|
" a > b , c ~ d e .f #g :h i.j [ k ] { color: red }",
|
|
|
|
"a > b, c ~ d e .f #g :h i.j [k] {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod css_units {
|
|
|
|
use super::StyleSheet;
|
|
|
|
test!(unit_none, "a {\n height: 1;\n}\n");
|
|
|
|
test!(unit_not_attached, "a {\n height: 1 px;\n}\n");
|
|
|
|
test!(unit_px, "a {\n height: 1px;\n}\n");
|
|
|
|
test!(unit_em, "a {\n height: 1em;\n}\n");
|
|
|
|
test!(unit_rem, "a {\n height: 1rem;\n}\n");
|
|
|
|
test!(unit_percent, "a {\n height: 1%;\n}\n");
|
|
|
|
}
|
2020-01-08 20:39:05 -05:00
|
|
|
|
2020-01-14 20:23:05 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod css_comments {
|
|
|
|
use super::StyleSheet;
|
2020-01-08 20:39:05 -05:00
|
|
|
test!(
|
|
|
|
removes_inner_comments,
|
|
|
|
"a {\n color: red/* hi */;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
removes_inner_comments_whitespace,
|
|
|
|
"a {\n color: red /* hi */;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
preserves_outer_comments_before,
|
|
|
|
"a {\n /* hi */\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
preserves_outer_comments_after,
|
|
|
|
"a {\n color: red;\n /* hi */\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
preserves_outer_comments_two,
|
|
|
|
"a {\n /* foo */\n /* bar */\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
2020-01-09 20:56:09 -05:00
|
|
|
preserves_toplevel_comment_before,
|
2020-01-08 20:39:05 -05:00
|
|
|
"/* foo */\na {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-09 20:56:09 -05:00
|
|
|
test!(
|
|
|
|
preserves_toplevel_comment_after,
|
|
|
|
"a {\n color: red;\n}\n/* foo */\n"
|
|
|
|
);
|
2020-01-08 20:39:05 -05:00
|
|
|
test!(
|
|
|
|
removes_single_line_comment,
|
|
|
|
"// a { color: red }\na {\n height: 1 1px;\n}\n",
|
|
|
|
"a {\n height: 1 1px;\n}\n"
|
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
}
|
2020-01-11 16:12:23 -05:00
|
|
|
|
2020-01-14 20:23:05 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod css_styles {
|
|
|
|
use super::StyleSheet;
|
|
|
|
test!(basic_style, "a {\n color: red;\n}\n");
|
|
|
|
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
2020-01-11 16:12:23 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
two_inner_rulesets,
|
|
|
|
"a {\n b {\n color: red;\n}\n c {\n color: white;\n}\n}\n",
|
|
|
|
"a b {\n color: red;\n}\na c {\n color: white;\n}\n"
|
2020-01-11 16:12:23 -05:00
|
|
|
);
|
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
two_rulesets,
|
|
|
|
"a {\n color: red;\n}\nc {\n color: white;\n}\n"
|
2020-01-11 16:12:23 -05:00
|
|
|
);
|
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
two_inner_outer_rulesets,
|
|
|
|
"a {\n b {\n color: red;\n}\n c {\n color: white;\n}\n}\na {\n b {\n color: red;\n}\n c {\n color: white;\n}\n}\n",
|
|
|
|
"a b {\n color: red;\n}\na c {\n color: white;\n}\na b {\n color: red;\n}\na c {\n color: white;\n}\n"
|
2020-01-11 16:12:23 -05:00
|
|
|
);
|
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
removes_empty_outer_styles,
|
|
|
|
"a {\n b {\n color: red;\n }\n",
|
|
|
|
"a b {\n color: red;\n}\n"
|
2020-01-11 16:12:23 -05:00
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
test!(removes_empty_styles, "a {}\n", "");
|
2020-01-11 18:43:42 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
doesnt_eat_style_after_ruleset,
|
|
|
|
"a {\n b {\n color: red;\n}\n color: blue;\n}\n",
|
|
|
|
"a {\n color: blue;\n}\na b {\n color: red;\n}\n"
|
2020-01-11 18:43:42 -05:00
|
|
|
);
|
2020-01-12 10:54:46 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
multiline_style,
|
|
|
|
"a {\n color: red\n blue;\n}\n",
|
|
|
|
"a {\n color: red blue;\n}\n"
|
2020-01-12 10:54:46 -05:00
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
test!(hyphenated_style_property, "a {\n font-family: Arial;\n}\n");
|
|
|
|
test!(hyphenated_style_value, "a {\n color: Open-Sans;\n}\n");
|
2020-01-12 10:54:46 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
space_separated_style_value,
|
|
|
|
"a {\n border: solid red;\n}\n"
|
2020-01-12 10:54:46 -05:00
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
test!(single_quoted_style_value, "a {\n font: 'Open-Sans';\n}\n");
|
2020-01-12 10:54:46 -05:00
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
double_quoted_style_value,
|
|
|
|
"a {\n font: \"Open-Sans\";\n}\n"
|
2020-01-12 10:54:46 -05:00
|
|
|
);
|
|
|
|
test!(
|
2020-01-14 20:23:05 -05:00
|
|
|
comma_style_value,
|
|
|
|
"a {\n font: Open-Sans, sans-serif;\n}\n"
|
2020-01-12 10:54:46 -05:00
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_interpolation_start,
|
|
|
|
"a {\n #{c}olor: red;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_interpolation_middle,
|
|
|
|
"a {\n co#{l}or: red;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_interpolation_end,
|
|
|
|
"a {\n colo#{r}: red;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_interpolation_variable,
|
|
|
|
"$a: foo;\na {\n co#{$a}lor: red;\n}\n",
|
|
|
|
"a {\n cofoolor: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_val_interpolation_start,
|
|
|
|
"a {\n color: #{r}ed;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-11 18:43:42 -05:00
|
|
|
test!(
|
2020-01-12 10:54:46 -05:00
|
|
|
style_val_interpolation_middle,
|
|
|
|
"a {\n color: r#{e}d;\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_val_interpolation_end,
|
|
|
|
"a {\n color: re#{d};\n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_val_interpolation_variable,
|
|
|
|
"$a: foo;\na {\n color: r#{$a}ed;\n}\n",
|
|
|
|
"a {\n color: rfooed;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
style_whitespace,
|
|
|
|
"a {\n color : red ; \n}\n",
|
|
|
|
"a {\n color: red;\n}\n"
|
2020-01-11 18:43:42 -05:00
|
|
|
);
|
2020-01-05 20:51:14 -05:00
|
|
|
}
|
2020-01-14 20:23:05 -05:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod css_misc {
|
|
|
|
use super::*;
|
|
|
|
test!(
|
|
|
|
combines_hyphens,
|
|
|
|
"a {\n foo: bar - baz;\n}\n",
|
|
|
|
"a {\n foo: bar-baz;\n}\n"
|
|
|
|
);
|
|
|
|
test!(does_not_combine_hyphens, "a {\n foo: bar -baz;\n}\n");
|
|
|
|
test!(
|
|
|
|
ident_starts_with_hyphen,
|
|
|
|
"a {\n foo: -webkit-bar-baz;\n}\n"
|
|
|
|
);
|
|
|
|
test!(ident_with_num, "el1 {\n a: b;\n}\n");
|
|
|
|
test!(keyword_important, "a {\n height: 1 !important;\n}\n");
|
|
|
|
test!(
|
|
|
|
keyword_important_uppercase,
|
|
|
|
"a {\n height: 1 !IMPORTANT;\n}\n",
|
|
|
|
"a {\n height: 1 !important;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
keyword_important_not_at_end,
|
|
|
|
"a {\n height: !important 1;\n}\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod css_mixins {
|
|
|
|
use super::*;
|
2020-01-17 10:44:16 -05:00
|
|
|
test!(
|
|
|
|
basic_mixin,
|
|
|
|
"@mixin a {\n color: red;\n}\n\nb {\n @include a;\n}\n",
|
|
|
|
"b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_two_styles,
|
|
|
|
"@mixin a {\n color: red;\n color: blue;\n}\n\nb {\n @include a;\n}\n",
|
|
|
|
"b {\n color: red;\n color: blue;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_ruleset,
|
|
|
|
"@mixin a {\n b {\n color: red;\n }\n}\nb {\n @include a;\n}\n",
|
|
|
|
"b b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_two_rulesets,
|
|
|
|
"@mixin a {\n b {\n color: red;\n }\n c {\n color: blue;\n }\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d b {\n color: red;\n}\nd c {\n color: blue;\n}\n"
|
|
|
|
);
|
2020-01-17 16:14:19 -05:00
|
|
|
test!(
|
|
|
|
mixin_ruleset_and_style,
|
|
|
|
"@mixin a {\n b {\n color: red;\n }\n color: blue;\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d {\n color: blue;\n}\nd b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_style_and_ruleset,
|
|
|
|
"@mixin a {\n color: blue;\n b {\n color: red;\n}\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d {\n color: blue;\n}\nd b {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_nested_rulesets,
|
|
|
|
"@mixin a {\n b {\n c {\n color: red;\n}\n}\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d b c {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-17 21:15:38 -05:00
|
|
|
test!(
|
|
|
|
mixin_removes_empty_ruleset,
|
|
|
|
"@mixin a {\n color:red; b {\n}\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d {\n color: red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
mixin_variable_scope_one_ruleset,
|
|
|
|
"@mixin a {\n $a: blue;\nb {\n $a: red;\n} color: $a\n}\nd {\n @include a;\n}\n",
|
|
|
|
"d {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-14 20:23:05 -05:00
|
|
|
}
|