Skip to content

Commit df81481

Browse files
committed
Mixin improvements
Better handling of mixin call bodies, the `@content` item, and the `content_exists` function. Fixes #112.
1 parent e732f60 commit df81481

File tree

9 files changed

+53
-43
lines changed

9 files changed

+53
-43
lines changed

Diff for: CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ The format is based on
77
project adheres to
88
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

10+
## Unreleased
11+
12+
### Breaking changes
13+
14+
* The `sass::Item::MixinCall` enum alternative was modified.
15+
16+
### Improvements
17+
18+
* Better handling of mixin call bodies, the `@content` item, and the
19+
`content_exists` function.
20+
* Removed some debug printouts that was accidentally left in 0.21.0.
21+
22+
1023
## Release 0.21.0 - 2021-06-01
1124

1225
Progress: 3727 of 6171 tests passed in dart-sass compatibility mode.

Diff for: src/output/transform.rs

+13-19
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ fn handle_item(
313313
Mixin {
314314
args: FormalArgs::none(),
315315
scope,
316-
body: body.clone(),
316+
body: body.clone().unwrap_or_else(Mixin::no_body),
317317
},
318318
);
319319
handle_body(
@@ -332,24 +332,18 @@ fn handle_item(
332332
}
333333
}
334334
Item::Content => {
335-
if let Some(rule) = rule {
336-
if let Some(content) =
337-
scope.get_mixin(&Name::from_static("%%BODY%%"))
338-
{
339-
let sel = scope.get_selectors().clone();
340-
handle_body(
341-
&content.body,
342-
head,
343-
Some(rule),
344-
buf,
345-
ScopeRef::sub_selectors(content.scope, sel),
346-
file_context,
347-
)?;
348-
}
349-
} else {
350-
return Err(Error::S(
351-
"@content not allowed in global context".into(),
352-
));
335+
if let Some(content) =
336+
scope.get_mixin(&Name::from_static("%%BODY%%"))
337+
{
338+
let sel = scope.get_selectors().clone();
339+
handle_body(
340+
&content.body,
341+
head,
342+
rule,
343+
buf,
344+
ScopeRef::sub_selectors(content.scope, sel),
345+
file_context,
346+
)?;
353347
}
354348
}
355349

Diff for: src/parser/mod.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,7 @@ fn mixin_call2(input: Span) -> PResult<Item> {
236236
opt(body_block),
237237
terminated(opt_spacelike, opt(tag(";"))),
238238
)(input)?;
239-
Ok((
240-
input,
241-
Item::MixinCall(
242-
name,
243-
args.unwrap_or_default(),
244-
body.unwrap_or_default(),
245-
),
246-
))
239+
Ok((input, Item::MixinCall(name, args.unwrap_or_default(), body)))
247240
}
248241

249242
/// What follows an `@` sign
@@ -619,7 +612,7 @@ fn if_with_no_else() {
619612
fn test_mixin_call_noargs() {
620613
assert_eq!(
621614
check_parse!(mixin_call, b"@include foo;"),
622-
Item::MixinCall("foo".to_string(), CallArgs::default(), vec![]),
615+
Item::MixinCall("foo".to_string(), CallArgs::default(), None),
623616
)
624617
}
625618

@@ -630,7 +623,7 @@ fn test_mixin_call_pos_args() {
630623
Item::MixinCall(
631624
"foo".to_string(),
632625
CallArgs::new(vec![(None, string("bar")), (None, string("baz"))]),
633-
vec![],
626+
None,
634627
),
635628
)
636629
}
@@ -645,7 +638,7 @@ fn test_mixin_call_named_args() {
645638
(Some("x".into()), string("bar")),
646639
(Some("y".into()), string("baz")),
647640
]),
648-
vec![],
641+
None,
649642
),
650643
)
651644
}

Diff for: src/sass/functions/meta.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{check, get_opt_check, get_string, Error, FunctionMap};
22
use crate::css::{CallArgs, Value};
3-
use crate::sass::Name;
3+
use crate::sass::{Mixin, Name};
44
use crate::{Format, Scope, ScopeRef};
55

66
pub fn create_module() -> Scope {
@@ -37,8 +37,14 @@ pub fn create_module() -> Scope {
3737
}
3838
});
3939
def!(f, content_exists(), |s| {
40-
let content = s.get_mixin(&Name::from_static("%%BODY%%"));
41-
Ok(content.map(|m| !m.body.is_empty()).unwrap_or(false).into())
40+
let content = call_scope(s).get_mixin(&Name::from_static("%%BODY%%"));
41+
if let Some(content) = content {
42+
Ok((!Mixin::is_no_body(&content.body)).into())
43+
} else {
44+
Err(Error::error(
45+
"content-exists() may only be called within a mixin",
46+
))
47+
}
4248
});
4349
def!(f, feature_exists(feature), |s| {
4450
let (feature, _q) = get_string(s, "feature")?;

Diff for: src/sass/item.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub enum Item {
4242
/// A `@mixin` directive, declaring a mixin.
4343
MixinDeclaration(String, FormalArgs, Vec<Item>),
4444
/// An `@include` directive, calling a mixin.
45-
MixinCall(String, CallArgs, Vec<Item>),
45+
MixinCall(String, CallArgs, Option<Vec<Item>>),
4646
/// An `@content` directive (in a mixin declaration).
4747
Content,
4848

Diff for: src/sass/mixin.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::sass::{FormalArgs, Item};
1+
use crate::sass::{FormalArgs, Item, Value};
22
use crate::ScopeRef;
33

44
/// A mixin is a callable body of items.
@@ -11,3 +11,13 @@ pub struct Mixin {
1111
/// The body of this mixin.
1212
pub body: Vec<Item>,
1313
}
14+
15+
impl Mixin {
16+
/// An illegal mixin body, used for `@content` on mixin calls sans body.
17+
pub(crate) fn no_body() -> Vec<Item> {
18+
vec![Item::Property("%%NO-BODY%%".into(), Value::Null)]
19+
}
20+
pub(crate) fn is_no_body(body: &[Item]) -> bool {
21+
body == [Item::Property("%%NO-BODY%%".into(), Value::Null)]
22+
}
23+
}

Diff for: tests/spec/core_functions/meta/content_exists.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ mod controls {
3030
);
3131
}
3232
#[test]
33-
#[ignore] // wrong result
3433
fn test_true() {
3534
assert_eq!(
3635
runner().ok("// Regression test for sass/libsass#2842\
@@ -80,7 +79,7 @@ mod error {
8079
);
8180
}
8281
#[test]
83-
#[ignore] // missing error
82+
#[ignore] // wrong error
8483
fn in_function_called_by_mixin() {
8584
assert_eq!(
8685
runner().err(
@@ -103,7 +102,6 @@ mod error {
103102
);
104103
}
105104
#[test]
106-
#[ignore] // missing error
107105
fn outside_mixin() {
108106
assert_eq!(
109107
runner().err("a {b: content-exists()}\n"),
@@ -144,7 +142,6 @@ mod test_false {
144142
use super::runner;
145143

146144
#[test]
147-
#[ignore] // unexepected error
148145
fn through_content() {
149146
assert_eq!(
150147
runner().ok("@mixin call-content {\
@@ -179,7 +176,6 @@ mod test_true {
179176
use super::runner;
180177

181178
#[test]
182-
#[ignore] // unexepected error
183179
fn empty() {
184180
assert_eq!(
185181
runner().ok("@mixin a {\
@@ -193,7 +189,6 @@ mod test_true {
193189
);
194190
}
195191
#[test]
196-
#[ignore] // unexepected error
197192
fn non_empty() {
198193
assert_eq!(
199194
runner().ok("@mixin a {\

Diff for: tests/spec/libsass_closed_issues/issue_1640.rs

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner {
66
}
77

88
#[test]
9-
#[ignore] // unexepected error
109
fn test() {
1110
assert_eq!(
1211
runner().ok("@mixin foo() {\

Diff for: tests/spec/libsass_closed_issues/issue_1644/mixin_parent.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner {
66
}
77

88
#[test]
9-
#[ignore] // wrong error
9+
#[ignore] // missing error
1010
fn test() {
1111
assert_eq!(
1212
runner().err(

0 commit comments

Comments
 (0)