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

feat(css/compat): port not #6668

Merged
merged 3 commits into from Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 16 additions & 2 deletions crates/swc_css_compat/src/compiler/mod.rs
@@ -1,7 +1,8 @@
use swc_common::{Spanned, DUMMY_SP};
use swc_css_ast::{
AbsoluteColorBase, AtRule, ComponentValue, MediaAnd, MediaCondition, MediaConditionAllType,
MediaConditionWithoutOr, MediaInParens, MediaQuery, Rule, SupportsCondition,
AbsoluteColorBase, AtRule, ComponentValue, CompoundSelector, MediaAnd, MediaCondition,
MediaConditionAllType, MediaConditionWithoutOr, MediaInParens, MediaQuery, Rule,
SupportsCondition,
};
use swc_css_visit::{VisitMut, VisitMutWith};

Expand All @@ -14,6 +15,7 @@ mod color_space_separated_parameters;
mod custom_media;
mod legacy_rgb_and_hsl;
mod media_query_ranges;
mod selector_not;
mod utils;

/// Compiles a modern CSS file to a CSS file which works with old browsers.
Expand Down Expand Up @@ -123,6 +125,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 @@ -9,5 +9,6 @@ bitflags! {
const COLOR_ALPHA_PARAMETER = 1 << 4;
const COLOR_SPACE_SEPARATED_PARAMETERS = 1 << 5;
const COLOR_LEGACY_RGB_AND_HSL = 1 << 6;
const SELECTOR_NOT = 1 << 7;
}
}
26 changes: 21 additions & 5 deletions crates/swc_css_compat/tests/fixture.rs
Expand Up @@ -62,7 +62,6 @@ fn test_nesting(input: PathBuf, suffix: Option<&str>) {
};

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

Expand All @@ -87,7 +86,6 @@ fn test_custom_media_query(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);

Expand All @@ -109,7 +107,6 @@ fn test_media_query_ranges(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);

Expand All @@ -131,7 +128,6 @@ fn test_color_hex_alpha(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);

Expand All @@ -153,7 +149,6 @@ fn test_color_space_separated_function_notation(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);

Expand All @@ -171,3 +166,24 @@ fn test_color_space_separated_function_notation(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;
}