Skip to content

Commit d445b69

Browse files
committed
Improve checking.
Error if trying to output invalid css values.
1 parent eceadb1 commit d445b69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+457
-493
lines changed

Diff for: src/css/binop.rs

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use super::{is_function_name, Value};
2+
use crate::output::{Format, Formatted};
3+
use crate::value::{CssDimension, Numeric, Operator};
4+
use crate::Invalid;
5+
use std::fmt::{self, Display, Write};
6+
7+
/// A binary operation.
8+
#[derive(Clone, Debug, Eq, PartialOrd)]
9+
pub struct BinOp {
10+
a: Value,
11+
s1: bool,
12+
op: Operator,
13+
s2: bool,
14+
b: Value,
15+
}
16+
17+
impl BinOp {
18+
pub(crate) fn new(
19+
a: Value,
20+
s1: bool,
21+
op: Operator,
22+
s2: bool,
23+
b: Value,
24+
) -> Self {
25+
BinOp { a, s1, op, s2, b }
26+
}
27+
28+
pub(crate) fn op(&self) -> Operator {
29+
self.op
30+
}
31+
pub(crate) fn a(&self) -> &Value {
32+
&self.a
33+
}
34+
pub(crate) fn b(&self) -> &Value {
35+
&self.b
36+
}
37+
38+
/// Get this value, but marked as calculated.
39+
///
40+
/// Make sure arithmetic operators are evaluated.
41+
pub fn into_calculated(self) -> Value {
42+
match self
43+
.op
44+
.eval(self.a.clone().into_calculated(), self.b.clone())
45+
{
46+
Ok(Some(v)) => v,
47+
_ => self.into(),
48+
}
49+
}
50+
51+
/// Validates that this operation is valid in css, even outside of
52+
/// a `calc(...)` value.
53+
pub(crate) fn valid_css(self) -> Result<Self, Invalid> {
54+
fn undef_op(v: BinOp) -> Invalid {
55+
Invalid::AtError(format!(
56+
"Undefined operation \"{}\".",
57+
v.format(Format::introspect())
58+
))
59+
}
60+
match self.op {
61+
Operator::Div => {
62+
self.a.clone().valid_css()?;
63+
self.b.clone().valid_css()?;
64+
Ok(self)
65+
}
66+
Operator::Plus | Operator::Minus => {
67+
if self.a.is_calculation() || self.b.is_calculation() {
68+
Err(undef_op(self))
69+
} else {
70+
fn cmp_unit(
71+
x: &Numeric,
72+
) -> Option<Vec<(CssDimension, i8)>> {
73+
let u = &x.unit;
74+
if u.is_known() && !u.is_percent() {
75+
Some(u.css_dimension())
76+
} else {
77+
None
78+
}
79+
}
80+
if let (Value::Numeric(a, _), Value::Numeric(b, _)) =
81+
(&self.a, &self.b)
82+
{
83+
if let (Some(a_u), Some(b_u)) =
84+
(cmp_unit(a), cmp_unit(b))
85+
{
86+
if a_u != b_u {
87+
return Err(Invalid::AtError(format!(
88+
"{} and {} have incompatible units.",
89+
a.format(Format::introspect()),
90+
b.format(Format::introspect()),
91+
)));
92+
}
93+
}
94+
}
95+
96+
Ok(self)
97+
}
98+
}
99+
_ => Err(undef_op(self)),
100+
}
101+
}
102+
103+
/// Format this operation.
104+
pub fn format(&self, format: Format) -> Formatted<BinOp> {
105+
Formatted {
106+
value: self,
107+
format,
108+
}
109+
}
110+
}
111+
112+
impl PartialEq for BinOp {
113+
fn eq(&self, other: &BinOp) -> bool {
114+
self.op == other.op && self.a == other.a && self.b == other.b
115+
}
116+
}
117+
118+
impl From<BinOp> for Value {
119+
fn from(value: BinOp) -> Self {
120+
Value::BinOp(Box::new(value))
121+
}
122+
}
123+
124+
impl Display for Formatted<'_, BinOp> {
125+
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
126+
if self.value.op == Operator::Plus
127+
&& (add_as_join(&self.value.a) || add_as_join(&self.value.b))
128+
{
129+
// The plus operator is also a concat operator
130+
self.value.a.format(self.format).fmt(out)?;
131+
self.value.b.format(self.format).fmt(out)
132+
} else {
133+
use Operator::{Minus, Plus};
134+
// Note: This simplification should probably be done in eval.
135+
let (op, b) = match (self.value.op, &self.value.b) {
136+
(Plus, Value::Numeric(v, _)) if v.value.is_negative() => {
137+
(Minus, Value::from(-v))
138+
}
139+
(Minus, Value::Numeric(v, _)) if v.value.is_negative() => {
140+
(Plus, Value::from(-v))
141+
}
142+
(op, Value::Paren(p)) => {
143+
if let Some(op2) = is_op(p.as_ref()) {
144+
if op2 > op {
145+
(op, *p.clone())
146+
} else {
147+
(op, self.value.b.clone())
148+
}
149+
} else {
150+
(op, self.value.b.clone())
151+
}
152+
}
153+
(op, Value::BinOp(op2))
154+
if (op2.op < op) || (op == Minus && op2.op == Minus) =>
155+
{
156+
(op, Value::Paren(Box::new(self.value.b.clone())))
157+
}
158+
(op, v) => (op, v.clone()),
159+
};
160+
fn is_op(v: &Value) -> Option<Operator> {
161+
match v {
162+
Value::BinOp(op) => Some(op.op),
163+
_ => None,
164+
}
165+
}
166+
self.value.a.format(self.format).fmt(out)?;
167+
if self.value.s1 {
168+
out.write_char(' ')?;
169+
}
170+
op.fmt(out)?;
171+
if self.value.s2 {
172+
out.write_char(' ')?;
173+
}
174+
b.format(self.format).fmt(out)
175+
}
176+
}
177+
}
178+
179+
fn add_as_join(v: &Value) -> bool {
180+
match v {
181+
Value::List(..) => true,
182+
Value::Literal(ref s) => !s.is_css_fn(),
183+
Value::Call(ref name, _) => !is_function_name(name),
184+
Value::BinOp(op) => {
185+
op.op == Operator::Plus
186+
&& (add_as_join(&op.a) || add_as_join(&op.b))
187+
}
188+
Value::True | Value::False => true,
189+
_ => false,
190+
}
191+
}

Diff for: src/css/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Types for css values and rules.
22
mod atrule;
3+
mod binop;
34
mod call_args;
45
mod comment;
56
mod item;
@@ -11,12 +12,13 @@ mod value;
1112
mod valueformat;
1213

1314
pub use self::atrule::{AtRule, AtRuleBodyItem};
15+
pub use self::binop::BinOp;
1416
pub use self::call_args::CallArgs;
1517
pub use self::comment::Comment;
1618
pub use self::item::{Import, Item};
1719
pub use self::rule::{BodyItem, Property, Rule};
1820
pub use self::selectors::{BadSelector, Selector, SelectorPart, Selectors};
1921
pub use self::string::CssString;
20-
pub use self::value::{BinOp, Value, ValueMap, ValueToMapError};
22+
pub use self::value::{Value, ValueMap, ValueToMapError};
2123

2224
pub(crate) use self::util::{is_calc_name, is_function_name, is_not};

0 commit comments

Comments
 (0)