Skip to content

Commit

Permalink
feat(css/compat): port not
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Dec 16, 2022
1 parent fc6ed6b commit 1f94210
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 1 deletion.
15 changes: 14 additions & 1 deletion crates/swc_css_compat/src/compiler/mod.rs
@@ -1,6 +1,6 @@
use swc_common::{Spanned, DUMMY_SP};
use swc_css_ast::{
AtRule, ComponentValue, MediaAnd, MediaCondition, MediaConditionAllType,
AtRule, ComponentValue, CompoundSelector, MediaAnd, MediaCondition, MediaConditionAllType,
MediaConditionWithoutOr, MediaInParens, MediaQuery, Rule, SupportsCondition,
};
use swc_css_visit::{VisitMut, VisitMutWith};
Expand All @@ -11,6 +11,7 @@ use crate::feature::Features;
mod color_hex_alpha;
mod custom_media;
mod media_query_ranges;
mod selector_not;

/// Compiles a modern CSS file to a CSS file which works with old browsers.
#[derive(Debug)]
Expand Down Expand Up @@ -119,6 +120,18 @@ impl VisitMut for Compiler {
}
}

fn visit_mut_compound_selector(&mut self, n: &mut CompoundSelector) {
n.visit_mut_children_with(self);

if self.in_supports_condition {
return;
}

if self.c.process.contains(Features::SELECTOR_NOT) {
self.process_selector_not(n);
}
}

fn visit_mut_component_value(&mut self, n: &mut ComponentValue) {
n.visit_mut_children_with(self);

Expand Down
55 changes: 55 additions & 0 deletions crates/swc_css_compat/src/compiler/selector_not.rs
@@ -0,0 +1,55 @@
use swc_atoms::js_word;
use swc_css_ast::{
CompoundSelector, PseudoClassSelector, PseudoClassSelectorChildren, SelectorList,
SubclassSelector,
};

use crate::compiler::Compiler;

impl Compiler {
pub(crate) fn process_selector_not(&mut self, n: &mut CompoundSelector) {
let has_not = n.subclass_selectors.iter().any(|n| matches!(n, SubclassSelector::PseudoClass(PseudoClassSelector { name, children: Some(children), ..}) if name.value == js_word!("not")
&& matches!(children.get(0), Some(PseudoClassSelectorChildren::SelectorList(selector_list)) if selector_list.children.len() > 1)));

if !has_not {
return;
}

let mut new_subclass_selectors = Vec::with_capacity(n.subclass_selectors.len());

for selector in &mut n.subclass_selectors.drain(..) {
match selector {
SubclassSelector::PseudoClass(PseudoClassSelector {
span,
name,
children: Some(children),
..
}) if name.value == js_word!("not")
&& matches!(children.get(0), Some(PseudoClassSelectorChildren::SelectorList(selector_list)) if selector_list.children.len() > 1) =>
{
if let Some(PseudoClassSelectorChildren::SelectorList(selector_list)) =
children.get(0)
{
for child in &selector_list.children {
new_subclass_selectors.push(SubclassSelector::PseudoClass(
PseudoClassSelector {
span,
name: name.clone(),
children: Some(vec![
PseudoClassSelectorChildren::SelectorList(SelectorList {
span: child.span,
children: vec![child.clone()],
}),
]),
},
));
}
}
}
_ => new_subclass_selectors.push(selector),
}
}

n.subclass_selectors = new_subclass_selectors;
}
}
1 change: 1 addition & 0 deletions crates/swc_css_compat/src/feature.rs
Expand Up @@ -6,5 +6,6 @@ bitflags! {
const CUSTOM_MEDIA = 1 << 1;
const MEDIA_QUERY_RANGES = 1 << 2;
const COLOR_HEX_ALPHA = 1 << 3;
const SELECTOR_NOT = 1 << 7;
}
}
22 changes: 22 additions & 0 deletions crates/swc_css_compat/tests/fixture.rs
Expand Up @@ -147,3 +147,25 @@ fn test_color_hex_alpha(input: PathBuf) {
})
.unwrap();
}

#[testing::fixture("tests/selector-not/**/*.css", exclude("expect.css"))]
fn test_selector_not(input: PathBuf) {
let output = input.with_extension("expect.css");

testing::run_test(false, |cm, _| {
//
let fm = cm.load_file(&input).unwrap();
let mut ss = parse_stylesheet(&fm);

ss.visit_mut_with(&mut Compiler::new(Config {
process: Features::SELECTOR_NOT,
}));

let s = print_stylesheet(&ss);

NormalizedOutput::from(s).compare_to_file(&output).unwrap();

Ok(())
})
.unwrap();
}
101 changes: 101 additions & 0 deletions crates/swc_css_compat/tests/selector-not/input.css
@@ -0,0 +1,101 @@
body {
order: 1;
}

body, Not {
order: 2;
}

em[attr=:not],
em[attr=":not"] {
order: 3;
}

em[attr~=:not],
em[attr~=":not"] {
order: 4;
}

em[not=abc],
em[not="abc"] {
order: 5;
}

:not {
order: 6;
}

:not(a, b) {
order: 7;
}

:nOt(a, b) {
order: 7.1;
}

tag:not(.class, .class2) {
order: 8;
}

tag :not(tag2, tag3) {
order: 9;
}

tag :not(tag2, tag3) :not(tag4, tag5) {
order: 10;
}

tag :not(tag2, tag3) :not(tag4, tag5), test {
order: 11;
}

tag :not(tag2 :not(tag4, tag5), tag3) {
order: 12;
}

.foo:not(:nth-child(-n+2), .bar) {
order: 13;
}

a:not(.b,
.c) {
order: 14;
}

.foo:not(:hover, :focus)::before {
order: 15;
}

.foo\\:not-italic {
order: 16;
}

.foo\\:not-italic:not(:hover, :focus) {
order: 17;
}

:not :dir(ltr) {
order: 18;
}

:not(something > complex, other) {
order: 19;
}

div:not([style*="(120, 60, 12"]) {
order: 20;
}

@supports selector(:not(something > complex, other)) {
:not(something > complex, other) {
order: 19;
}
}

:not(h1, h2, h3) {
color: red;
}

:not(h1) {
color: red;
}
79 changes: 79 additions & 0 deletions crates/swc_css_compat/tests/selector-not/input.expect.css
@@ -0,0 +1,79 @@
body {
order: 1;
}
body,
Not {
order: 2;
}
em[attr=:not],
em[attr=":not"] {
order: 3;
}
em[attr~=:not],
em[attr~=":not"] {
order: 4;
}
em[not=abc],
em[not="abc"] {
order: 5;
}
:not {
order: 6;
}
:not(a):not(b) {
order: 7;
}
:nOt(a):nOt(b) {
order: 7.1;
}
tag:not(.class):not(.class2) {
order: 8;
}
tag :not(tag2):not(tag3) {
order: 9;
}
tag :not(tag2):not(tag3) :not(tag4):not(tag5) {
order: 10;
}
tag :not(tag2):not(tag3) :not(tag4):not(tag5),
test {
order: 11;
}
tag :not(tag2 :not(tag4):not(tag5)):not(tag3) {
order: 12;
}
.foo:not(:nth-child(-n+2)):not(.bar) {
order: 13;
}
a:not(.b):not(.c) {
order: 14;
}
.foo:not(:hover):not(:focus)::before {
order: 15;
}
.foo\\:not-italic {
order: 16;
}
.foo\\:not-italic:not(:hover):not(:focus) {
order: 17;
}
:not :dir(ltr) {
order: 18;
}
:not(something > complex):not(other) {
order: 19;
}
div:not([style*="(120, 60, 12"]) {
order: 20;
}
@supports selector(:not(something > complex, other)) {
:not(something > complex):not(other) {
order: 19;
}
}
:not(h1):not(h2):not(h3) {
color: red;
}
:not(h1) {
color: red;
}

0 comments on commit 1f94210

Please sign in to comment.