ambiguous keyword operators are not treated as function calls

This commit is contained in:
Connor Skees 2020-08-18 03:06:52 -04:00
parent 00a7659e69
commit 48de92fdc0
2 changed files with 118 additions and 63 deletions

View File

@ -232,6 +232,65 @@ impl<'a> Parser<'a> {
)
}
fn parse_fn_call(
&mut self,
mut s: String,
lower: String,
) -> SassResult<Spanned<IntermediateValue>> {
if lower == "min" || lower == "max" {
match self.try_parse_min_max(&lower, true)? {
Some(val) => {
self.toks.truncate_iterator_to_cursor();
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(val, QuoteKind::None),
))
.span(self.span_before));
}
None => {
self.toks.reset_cursor();
}
}
}
let as_ident = Identifier::from(&s);
let func = match self.scopes.get_fn(as_ident, self.global_scope) {
Some(f) => f,
None => {
if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) {
return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
SassFunction::Builtin(f.clone(), as_ident),
self.parse_call_args()?,
))
.span(self.span_before));
} else {
// check for special cased CSS functions
match unvendor(&lower) {
"calc" | "element" | "expression" => {
s = lower;
self.parse_calc_args(&mut s)?;
}
"url" => match self.try_parse_url()? {
Some(val) => s = val,
None => s.push_str(&self.parse_call_args()?.to_css_string()?),
},
_ => s.push_str(&self.parse_call_args()?.to_css_string()?),
}
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(s, QuoteKind::None),
))
.span(self.span_before));
}
}
};
let call_args = self.parse_call_args()?;
Ok(
IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args))
.span(self.span_before),
)
}
fn parse_ident_value(
&mut self,
predicate: &dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
@ -255,72 +314,22 @@ impl<'a> Parser<'a> {
});
}
match self.toks.peek() {
Some(Token { kind: '(', .. }) => {
self.toks.next();
if lower == "min" || lower == "max" {
match self.try_parse_min_max(&lower, true)? {
Some(val) => {
self.toks.truncate_iterator_to_cursor();
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(val, QuoteKind::None),
))
.span(span));
}
None => {
self.toks.reset_cursor();
}
}
}
let as_ident = Identifier::from(&s);
let func = match self.scopes.get_fn(as_ident, self.global_scope) {
Some(f) => f,
None => {
if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) {
return Ok(IntermediateValue::Value(
HigherIntermediateValue::Function(
SassFunction::Builtin(f.clone(), as_ident),
self.parse_call_args()?,
),
)
.span(span));
} else {
// check for special cased CSS functions
match unvendor(&lower) {
"calc" | "element" | "expression" => {
s = lower;
self.parse_calc_args(&mut s)?;
}
"url" => match self.try_parse_url()? {
Some(val) => s = val,
None => s.push_str(&self.parse_call_args()?.to_css_string()?),
},
_ => s.push_str(&self.parse_call_args()?.to_css_string()?),
}
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(s, QuoteKind::None),
))
.span(span));
}
}
};
let call_args = self.parse_call_args()?;
return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
func, call_args,
))
.span(span));
}
Some(Token { kind: '.', .. }) => {
if !predicate(self.toks) {
if !is_keyword_operator(&s) {
match self.toks.peek() {
Some(Token { kind: '(', .. }) => {
self.span_before = span;
self.toks.next();
return self.parse_module_item(&s, span);
return self.parse_fn_call(s, lower);
}
Some(Token { kind: '.', .. }) => {
if !predicate(self.toks) {
self.toks.next();
return self.parse_module_item(&s, span);
}
}
_ => {}
}
_ => {}
}
// check for named colors
@ -1356,3 +1365,7 @@ fn parse_i64(s: &str) -> i64 {
.iter()
.fold(0, |total, this| total * 10 + i64::from(this - b'0'))
}
fn is_keyword_operator(s: &str) -> bool {
matches!(s, "and" | "or" | "not")
}

View File

@ -41,3 +41,45 @@ test!(
"a {\n $primary: #f2ece4;\n $accent: #e1d7d2;\n color: radial-gradient($primary, $accent);\n}\n",
"a {\n color: radial-gradient(#f2ece4, #e1d7d2);\n}\n"
);
test!(
fn_named_not_is_evaluated_as_unary_op,
"a {\n color: not(true);\n}\n",
"a {\n color: false;\n}\n"
);
test!(
fn_named_true_is_plain_css,
"a {\n color: true(true);\n}\n",
"a {\n color: true(true);\n}\n"
);
test!(
fn_named_false_is_plain_css,
"a {\n color: false(true);\n}\n",
"a {\n color: false(true);\n}\n"
);
test!(
fn_named_null_is_plain_css,
"a {\n color: null(true);\n}\n",
"a {\n color: null(true);\n}\n"
);
test!(
fn_named_and_is_evaluated_as_binop,
"a {\n color: true and(foo);\n}\n",
"a {\n color: foo;\n}\n"
);
test!(
fn_named_or_is_evaluated_as_binop,
"a {\n color: true or(foo);\n}\n",
"a {\n color: true;\n}\n"
);
test!(
#[ignore = "this is not currently parsed correctly"]
fn_named_and_alone_is_not_evaluated_as_binop,
"a {\n color: and(foo);\n}\n",
"a {\n color: and(foo);\n}\n"
);
test!(
#[ignore = "this is not currently parsed correctly"]
fn_named_or_alone_is_not_evaluated_as_binop,
"a {\n color: or(foo);\n}\n",
"a {\n color: or(foo);\n}\n"
);