Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Support :global directives #778

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 57 additions & 24 deletions src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ impl<'i> SelectorImpl<'i> for Selectors {

fn to_css<W: fmt::Write>(selectors: &SelectorList<'i>, dest: &mut W) -> std::fmt::Result {
let mut printer = Printer::new(dest, PrinterOptions::default());
serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error)
serialize_selector_list(selectors.0.iter(), &mut printer, None, false, selectors.0.first())
.map_err(|_| std::fmt::Error)
}
}

Expand Down Expand Up @@ -190,6 +191,9 @@ impl<'a, 'o, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'o,
"corner-present" => WebKitScrollbar(WebKitScrollbarPseudoClass::CornerPresent),
"window-inactive" => WebKitScrollbar(WebKitScrollbarPseudoClass::WindowInactive),

"local" if self.options.css_modules.is_some() => LocalNoSelector,
"global" if self.options.css_modules.is_some() => GlobalNoSelector,

_ => {
if !name.starts_with('-') {
self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
Expand Down Expand Up @@ -515,6 +519,11 @@ pub enum PseudoClass<'i> {
selector: Box<Selector<'i>>,
},

/// The CSS modules :local selector.
LocalNoSelector,
/// The CSS modules :global selector.
GlobalNoSelector,

/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.
// https://webkit.org/blog/363/styling-scrollbars/
#[cfg_attr(
Expand Down Expand Up @@ -611,7 +620,7 @@ impl<'i> cssparser::ToCss for PseudoClass<'i> {
W: fmt::Write,
{
let mut s = String::new();
serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None)
serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None, None)
.map_err(|_| std::fmt::Error)?;
write!(dest, "{}", s)
}
Expand All @@ -621,6 +630,7 @@ fn serialize_pseudo_class<'a, 'i, W>(
pseudo_class: &PseudoClass<'i>,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
nesting: Option<&Selector<'i>>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
Expand Down Expand Up @@ -759,10 +769,29 @@ where
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill
Autofill(prefix) => write_prefixed!(prefix, "autofill"),

Local { selector } => serialize_selector(selector, dest, context, false),
Local { selector } => serialize_selector(selector, dest, context, false, nesting),
Global { selector } => {
let css_module = std::mem::take(&mut dest.css_module);
serialize_selector(selector, dest, context, false)?;
serialize_selector(selector, dest, context, false, nesting)?;
dest.css_module = css_module;
Ok(())
}
LocalNoSelector => serialize_selector(
&nesting.expect("to_css() does not support :local nor :global"),
dest,
context,
false,
nesting,
),
GlobalNoSelector => {
let css_module = std::mem::take(&mut dest.css_module);
serialize_selector(
&nesting.expect("to_css() does not support :local nor :global"),
dest,
context,
false,
nesting,
)?;
dest.css_module = css_module;
Ok(())
}
Expand Down Expand Up @@ -1043,16 +1072,17 @@ impl<'i> cssparser::ToCss for PseudoElement<'i> {
W: fmt::Write,
{
let mut s = String::new();
serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None)
serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None, None)
.map_err(|_| std::fmt::Error)?;
write!(dest, "{}", s)
}
}

fn serialize_pseudo_element<'a, 'i, W>(
pseudo_element: &PseudoElement,
pseudo_element: &PseudoElement<'i>,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
css_modules_nesting: Option<&Selector<'i>>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
Expand Down Expand Up @@ -1094,12 +1124,12 @@ where
CueRegion => dest.write_str("::cue-region"),
CueFunction { selector } => {
dest.write_str("::cue(")?;
serialize_selector(selector, dest, context, false)?;
serialize_selector(selector, dest, context, false, css_modules_nesting)?;
dest.write_char(')')
}
CueRegionFunction { selector } => {
dest.write_str("::cue-region(")?;
serialize_selector(selector, dest, context, false)?;
serialize_selector(selector, dest, context, false, css_modules_nesting)?;
dest.write_char(')')
}
Placeholder(prefix) => {
Expand Down Expand Up @@ -1253,7 +1283,7 @@ impl<'a, 'i> ToCss for SelectorList<'i> {
where
W: fmt::Write,
{
serialize_selector_list(self.0.iter(), dest, dest.context(), false)
serialize_selector_list(self.0.iter(), dest, dest.context(), false, self.0.first())
}
}

Expand Down Expand Up @@ -1284,7 +1314,7 @@ impl<'a, 'i> ToCss for Selector<'i> {
where
W: fmt::Write,
{
serialize_selector(self, dest, dest.context(), false)
serialize_selector(self, dest, dest.context(), false, None)
}
}

Expand All @@ -1293,6 +1323,7 @@ fn serialize_selector<'a, 'i, W>(
dest: &mut Printer<W>,
context: Option<&StyleContext>,
mut is_relative: bool,
css_modules_nesting: Option<&Selector<'i>>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
Expand Down Expand Up @@ -1377,7 +1408,7 @@ where
}

for simple in iter {
serialize_component(simple, dest, context)?;
serialize_component(simple, dest, context, css_modules_nesting)?;
}

if swap_nesting {
Expand Down Expand Up @@ -1407,15 +1438,15 @@ where
// This ensures that the compiled selector is valid. e.g. (div.foo is valid, .foodiv is not).
let nesting = iter.next().unwrap();
let local = iter.next().unwrap();
serialize_component(local, dest, context)?;
serialize_component(local, dest, context, css_modules_nesting)?;

// Also check the next item in case of namespaces.
if first_non_namespace > first_index {
let local = iter.next().unwrap();
serialize_component(local, dest, context)?;
serialize_component(local, dest, context, css_modules_nesting)?;
}

serialize_component(nesting, dest, context)?;
serialize_component(nesting, dest, context, css_modules_nesting)?;
} else if has_leading_nesting && should_compile_nesting {
// Nesting selector may serialize differently if it is leading, due to type selectors.
iter.next();
Expand All @@ -1432,7 +1463,7 @@ where
continue;
}
}
serialize_component(simple, dest, context)?;
serialize_component(simple, dest, context, css_modules_nesting)?;
}
}

Expand All @@ -1457,9 +1488,10 @@ where
}

fn serialize_component<'a, 'i, W>(
component: &Component,
component: &Component<'i>,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
css_modules_nesting: Option<&Selector<'i>>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
Expand Down Expand Up @@ -1510,7 +1542,7 @@ where
Component::Is(ref selectors) => {
// If there's only one simple selector, serialize it directly.
if should_unwrap_is(selectors) {
serialize_selector(selectors.first().unwrap(), dest, context, false)?;
serialize_selector(selectors.first().unwrap(), dest, context, false, css_modules_nesting)?;
return Ok(());
}

Expand Down Expand Up @@ -1538,16 +1570,16 @@ where
}
_ => unreachable!(),
}
serialize_selector_list(list.iter(), dest, context, false)?;
serialize_selector_list(list.iter(), dest, context, false, css_modules_nesting)?;
dest.write_str(")")
}
Component::Has(ref list) => {
dest.write_str(":has(")?;
serialize_selector_list(list.iter(), dest, context, true)?;
serialize_selector_list(list.iter(), dest, context, true, css_modules_nesting)?;
dest.write_str(")")
}
Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context),
Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context),
Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context, css_modules_nesting),
Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context, css_modules_nesting),
Component::Nesting => serialize_nesting(dest, context, false),
Component::Class(ref class) => {
dest.write_char('.')?;
Expand Down Expand Up @@ -1605,10 +1637,10 @@ where
if ctx.selectors.0.len() == 1
&& (first || (!has_type_selector(&ctx.selectors.0[0]) && is_simple(&ctx.selectors.0[0])))
{
serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false)
serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false, None)
} else {
dest.write_str(":is(")?;
serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false)?;
serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false, ctx.selectors.0.first())?;
dest.write_char(')')
}
} else {
Expand Down Expand Up @@ -1662,6 +1694,7 @@ fn serialize_selector_list<'a, 'i: 'a, I, W>(
dest: &mut Printer<W>,
context: Option<&StyleContext>,
is_relative: bool,
nesting: Option<&Selector<'i>>,
) -> Result<(), PrinterError>
where
I: Iterator<Item = &'a Selector<'i>>,
Expand All @@ -1673,7 +1706,7 @@ where
dest.delim(',', false)?;
}
first = false;
serialize_selector(selector, dest, context, is_relative)?;
serialize_selector(selector, dest, context, is_relative, nesting)?;
}
Ok(())
}
Expand Down
56 changes: 56 additions & 0 deletions tests/cli_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,59 @@ fn browserslist_environment_from_browserslist_env() -> Result<(), Box<dyn std::e

Ok(())
}

#[test]
fn css_modules_pseudo_classes() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let infile = dir.child("test.css");
let outfile = assert_fs::NamedTempFile::new("out.css")?;
let modules_file = assert_fs::NamedTempFile::new("module.json")?;
infile.write_str(
r#"
.blue {
background: blue;

:global {
.red {
background: red;
}
}

&:global {
&.green {
background: green;
}
}
}
"#,
)?;

let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.arg("--targets=defaults");
cmd.arg("--nesting");
cmd.arg(infile.path());
cmd.arg("-o").arg(outfile.path());
cmd.arg("--css-modules").arg(modules_file.path());
cmd.assert().success();

outfile.assert(predicate::str::contains(indoc! {r#"
.blue {
background: blue;

:global {
.red {
background: red;
}
}

&:global {
&.green {
background: green;
}
}
}
"#}));

Ok(())
}
Loading