Skip to content

Commit

Permalink
Add some checks for calc.
Browse files Browse the repository at this point in the history
When the `calc` function evaluates partially (to a `calc(...)` call),
that call should be a valid css calc call.
  • Loading branch information
kaj committed Apr 19, 2022
1 parent 08a9256 commit 29233bc
Show file tree
Hide file tree
Showing 28 changed files with 107 additions and 262 deletions.
17 changes: 17 additions & 0 deletions src/parser/pos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ impl SourcePos {
p.length += len;
}
}
/// If the position is `calc(some-arg)`, change to only `some-arg`.
///
/// This is only used to make errors from rsass more similar to
/// dart-sass errors.
pub(crate) fn opt_in_calc(mut self) -> Self {
let p: &mut SourcePosImpl = Arc::make_mut(&mut self.p);
let s = "calc(";
let part = &p.line[p.line_pos - 1..];
if part.starts_with(s) && part.chars().nth(p.length - 1) == Some(')')
{
let len = s.chars().count();
p.line_pos += len;
p.length -= len;
p.length -= 1;
}
self
}

/// True if this is the position of something built-in.
pub fn is_builtin(&self) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn unit(input: Span) -> PResult<Unit> {
"vmax" => Unit::Vmax,
"cm" => Unit::Cm,
"mm" => Unit::Mm,
"q" => Unit::Q,
"Q" | "q" => Unit::Q,
"in" => Unit::In,
"pt" => Unit::Pt,
"pc" => Unit::Pc,
Expand Down
85 changes: 63 additions & 22 deletions src/sass/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::error::Error;
use crate::output::{Format, Formatted};
use crate::parser::SourcePos;
use crate::sass::{FormalArgs, Name};
use crate::value::Numeric;
use crate::value::{CssDimension, Numeric, Operator, Quotes};
use crate::{sass, Scope, ScopeRef};
use lazy_static::lazy_static;
use std::collections::BTreeMap;
Expand Down Expand Up @@ -240,40 +240,81 @@ lazy_static! {
_ => false,
}
}
fn in_calc(v: Value) -> Value {
// Note: None here is for unknown, e.g. the dimension of something that is not a number.
fn css_dim(v: &Value) -> Option<Vec<(CssDimension, i8)>> {
match v {
Value::Literal(s) => {
if s.quotes() == crate::value::Quotes::None
&& s.value().ends_with(')')
&& s.value().starts_with("calc(")
// TODO: Handle BinOp recursively (again) (or let in_calc return (Value, CssDimension)?)
Value::Numeric(num, _) => {
let u = &num.unit;
if u.is_known() && !u.is_percent() {
Some(u.css_dimension())
} else {
None
}
}
_ => None,
}
}
fn in_calc(v: Value) -> Result<Value, Error> {
match v {
Value::Literal(s) if s.quotes() == Quotes::None => {
if let Some(arg) = s
.value()
.strip_prefix("calc(")
.and_then(|s| s.strip_suffix(')'))
{
Value::Paren(Box::new(
s.value()[5..s.value().len() - 1].into(),
))
Ok(Value::Paren(Box::new(arg.into())))
} else {
s.into()
Ok(s.into())
}
}
Value::Call(name, args) => {
if name == "calc"
&& args.check_no_named().is_ok()
&& args.positional.len() == 1
{
Value::Paren(Box::new(
Ok(Value::Paren(Box::new(
args.positional.into_iter().next().unwrap(),
))
)))
} else {
Ok(Value::Call(name, args))
}
}
Value::BinOp(a, _, op, _, b) => {
let a = in_calc(*a)?;
let b = in_calc(*b)?;
if let (Some(adim), Some(bdim)) = (css_dim(&a), css_dim(&b)) {
if (op == Operator::Plus || op == Operator::Minus) && adim != bdim {
return Err(Error::error(format!(
"{} and {} are incompatible.",
a.format(Format::introspect()),
b.format(Format::introspect()),
)))
}
}
Ok(Value::BinOp(
Box::new(a),
true,
op,
true,
Box::new(b),
))
}
Value::Numeric(num, c) => {
if num.unit.valid_in_css() {
Ok(Value::Numeric(num, c))
} else {
Value::Call(name, args)
Err(Error::error(format!(
"Number {} isn't compatible with CSS calculations.",
num.format(Format::introspect())
)))
}
}
Value::BinOp(a, _, op, _, b) => Value::BinOp(
Box::new(in_calc(*a)),
true,
op,
true,
Box::new(in_calc(*b)),
),
v => v,
v @ Value::Paren(..) => Ok(v),
v => Err(Error::error(format!(
"Value {} can't be used in a calculation.",
v.format(Format::introspect())
))),
}
}
let v = s.get(&name!(expr))?;
Expand All @@ -282,7 +323,7 @@ lazy_static! {
} else {
Ok(Value::Call(
"calc".into(),
CallArgs::from_value(in_calc(v))?,
CallArgs::from_value(in_calc(v)?)?,
))
}
});
Expand Down
5 changes: 4 additions & 1 deletion src/sass/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ impl Value {
let msg = format!("Error: {}", msg);
Error::BadCall(msg, pos.clone(), None)
}
e => Error::BadCall(e.to_string(), pos.clone(), None),
e => {
let pos = pos.clone().opt_in_calc();
Error::BadCall(e.to_string(), pos, None)
}
};
let name = name.into();
if let Some(f) = scope
Expand Down
4 changes: 2 additions & 2 deletions src/value/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub enum Unit {
Cm,
/// `mm` unit, absolute length.
Mm,
/// `q` unit, absolute length (4q == 1mm).
/// `q` unit, absolute length (4Q == 1mm).
Q,
/// `in` unit, absolute length in inch.
In,
Expand Down Expand Up @@ -216,7 +216,7 @@ impl fmt::Display for Unit {
Unit::Vmax => write!(out, "vmax"),
Unit::Cm => write!(out, "cm"),
Unit::Mm => write!(out, "mm"),
Unit::Q => write!(out, "q"),
Unit::Q => write!(out, "Q"),
Unit::In => write!(out, "in"),
Unit::Pt => write!(out, "pt"),
Unit::Pc => write!(out, "pc"),
Expand Down
21 changes: 20 additions & 1 deletion src/value/unitset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ impl UnitSet {
pub fn is_none(&self) -> bool {
self.units.iter().all(|(u, _)| *u == Unit::None)
}
/// Check if this UnitSet conains only known units.
pub fn is_known(&self) -> bool {
!self
.units
.iter()
.any(|(u, _)| matches!(u, Unit::Unknown(_)))
}
/// Check if this UnitSet is the percent unit.
pub fn is_percent(&self) -> bool {
self.units == [(Unit::Percent, 1)]
Expand Down Expand Up @@ -66,6 +73,14 @@ impl UnitSet {
.filter(|(_d, power)| *power != 0)
.collect::<Vec<_>>()
}
pub(crate) fn valid_in_css(&self) -> bool {
let dim = self.css_dimension();
match &dim[..] {
[] => true,
[(_d, p)] => *p == 1,
_ => false,
}
}

/// Get a scaling factor to convert this unit to another unit.
///
Expand Down Expand Up @@ -219,7 +234,11 @@ impl Display for UnitSet {

fn write_one(out: &mut fmt::Formatter, u: &Unit, p: i8) -> fmt::Result {
u.fmt(out)?;
if p != 1 {
if (0..=3).contains(&p) {
for _ in 1..p {
write!(out, "*{}", u)?;
}
} else {
write!(out, "^{}", p)?;
}
Ok(())
Expand Down
6 changes: 0 additions & 6 deletions tests/spec/values/calculation/calc/error/complex_units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ mod denominator {
use super::runner;

#[test]
#[ignore] // missing error
fn from_variable() {
assert_eq!(
runner().err(
Expand All @@ -27,7 +26,6 @@ mod denominator {
);
}
#[test]
#[ignore] // missing error
fn within_calc() {
assert_eq!(
runner().err("a {b: calc(1% + 1 / 2px)}\n"),
Expand All @@ -45,7 +43,6 @@ mod multiple_numerator {
use super::runner;

#[test]
#[ignore] // missing error
fn from_variable() {
assert_eq!(
runner().err(
Expand All @@ -61,7 +58,6 @@ mod multiple_numerator {
);
}
#[test]
#[ignore] // missing error
fn within_calc() {
assert_eq!(
runner().err("a {b: calc(1% + 1px * 2px)}\n"),
Expand All @@ -79,7 +75,6 @@ mod numerator_and_denominator {
use super::runner;

#[test]
#[ignore] // missing error
fn from_variable() {
assert_eq!(
runner().err(
Expand All @@ -96,7 +91,6 @@ mod numerator_and_denominator {
);
}
#[test]
#[ignore] // missing error
fn within_calc() {
assert_eq!(
runner().err("a {b: calc(1% + 1s / 2px)}\n"),
Expand Down
Loading

0 comments on commit 29233bc

Please sign in to comment.