@@ -20,7 +20,11 @@ use swc_core::{
20
20
base:: sourcemap:: SourceMapBuilder ,
21
21
common:: { BytePos , FileName , LineCol , Span } ,
22
22
css:: {
23
- ast:: { SubclassSelector , TypeSelector , UrlValue } ,
23
+ ast:: {
24
+ ComplexSelector , ComplexSelectorChildren , CompoundSelector , ForgivingComplexSelector ,
25
+ ForgivingRelativeSelector , PseudoClassSelectorChildren , PseudoElementSelectorChildren ,
26
+ RelativeSelector , SubclassSelector , TypeSelector , UrlValue ,
27
+ } ,
24
28
codegen:: { writer:: basic:: BasicCssWriter , CodeGenerator } ,
25
29
modules:: { CssClassName , TransformConfig } ,
26
30
visit:: { VisitMut , VisitMutWith , VisitWith } ,
@@ -707,26 +711,100 @@ const CSS_MODULE_ERROR: &str =
707
711
708
712
/// We only vist top-level selectors.
709
713
impl swc_core:: css:: visit:: Visit for CssValidator {
710
- fn visit_complex_selector ( & mut self , n : & swc_core:: css:: ast:: ComplexSelector ) {
711
- if n. children . iter ( ) . all ( |sel| match sel {
712
- swc_core:: css:: ast:: ComplexSelectorChildren :: CompoundSelector ( sel) => {
713
- sel. subclass_selectors . iter ( ) . all ( |sel| {
714
- matches ! (
715
- sel,
716
- SubclassSelector :: Attribute { .. }
717
- | SubclassSelector :: PseudoClass ( ..)
718
- | SubclassSelector :: PseudoElement ( ..)
719
- )
720
- } ) && match & sel. type_selector . as_deref ( ) {
721
- Some ( TypeSelector :: TagName ( tag) ) => {
722
- !matches ! ( & * tag. name. value. value, "html" | "body" )
723
- }
724
- Some ( TypeSelector :: Universal ( ..) ) => true ,
714
+ fn visit_complex_selector ( & mut self , n : & ComplexSelector ) {
715
+ fn is_complex_not_pure ( sel : & ComplexSelector ) -> bool {
716
+ sel. children . iter ( ) . all ( |sel| match sel {
717
+ ComplexSelectorChildren :: CompoundSelector ( sel) => is_compound_not_pure ( sel) ,
718
+ ComplexSelectorChildren :: Combinator ( _) => true ,
719
+ } )
720
+ }
721
+
722
+ fn is_forgiving_selector_not_pure ( sel : & ForgivingComplexSelector ) -> bool {
723
+ match sel {
724
+ ForgivingComplexSelector :: ComplexSelector ( sel) => is_complex_not_pure ( sel) ,
725
+ ForgivingComplexSelector :: ListOfComponentValues ( _) => false ,
726
+ }
727
+ }
728
+
729
+ fn is_forgiving_relative_selector_not_pure ( sel : & ForgivingRelativeSelector ) -> bool {
730
+ match sel {
731
+ ForgivingRelativeSelector :: RelativeSelector ( sel) => {
732
+ is_relative_selector_not_pure ( sel)
733
+ }
734
+ ForgivingRelativeSelector :: ListOfComponentValues ( _) => false ,
735
+ }
736
+ }
737
+
738
+ fn is_relative_selector_not_pure ( sel : & RelativeSelector ) -> bool {
739
+ is_complex_not_pure ( & sel. selector )
740
+ }
741
+
742
+ fn is_compound_not_pure ( sel : & CompoundSelector ) -> bool {
743
+ sel. subclass_selectors . iter ( ) . all ( |sel| match sel {
744
+ SubclassSelector :: Attribute { .. } => true ,
745
+ SubclassSelector :: PseudoClass ( cls) => {
746
+ cls. name . value == "not"
747
+ || cls. name . value == "has"
748
+ || if let Some ( c) = & cls. children {
749
+ c. iter ( ) . all ( |c| match c {
750
+ PseudoClassSelectorChildren :: ComplexSelector ( sel) => {
751
+ is_complex_not_pure ( sel)
752
+ }
753
+
754
+ PseudoClassSelectorChildren :: CompoundSelector ( sel) => {
755
+ is_compound_not_pure ( sel)
756
+ }
757
+
758
+ PseudoClassSelectorChildren :: SelectorList ( sels) => {
759
+ sels. children . iter ( ) . all ( is_complex_not_pure)
760
+ }
761
+ PseudoClassSelectorChildren :: ForgivingSelectorList ( sels) => {
762
+ sels. children . iter ( ) . all ( is_forgiving_selector_not_pure)
763
+ }
764
+ PseudoClassSelectorChildren :: CompoundSelectorList ( sels) => {
765
+ sels. children . iter ( ) . all ( is_compound_not_pure)
766
+ }
767
+ PseudoClassSelectorChildren :: RelativeSelectorList ( sels) => {
768
+ sels. children . iter ( ) . all ( is_relative_selector_not_pure)
769
+ }
770
+ PseudoClassSelectorChildren :: ForgivingRelativeSelectorList (
771
+ sels,
772
+ ) => sels
773
+ . children
774
+ . iter ( )
775
+ . all ( is_forgiving_relative_selector_not_pure) ,
776
+
777
+ PseudoClassSelectorChildren :: Ident ( _)
778
+ | PseudoClassSelectorChildren :: Str ( _)
779
+ | PseudoClassSelectorChildren :: Delimiter ( _)
780
+ | PseudoClassSelectorChildren :: PreservedToken ( _)
781
+ | PseudoClassSelectorChildren :: AnPlusB ( _) => false ,
782
+ } )
783
+ } else {
784
+ true
785
+ }
786
+ }
787
+ SubclassSelector :: PseudoElement ( el) => match & el. children {
788
+ Some ( c) => c. iter ( ) . all ( |c| match c {
789
+ PseudoElementSelectorChildren :: CompoundSelector ( sel) => {
790
+ is_compound_not_pure ( sel)
791
+ }
792
+
793
+ _ => false ,
794
+ } ) ,
725
795
None => true ,
796
+ } ,
797
+ _ => false ,
798
+ } ) && match & sel. type_selector . as_deref ( ) {
799
+ Some ( TypeSelector :: TagName ( tag) ) => {
800
+ !matches ! ( & * tag. name. value. value, "html" | "body" )
726
801
}
802
+ Some ( TypeSelector :: Universal ( ..) ) => true ,
803
+ None => true ,
727
804
}
728
- swc_core:: css:: ast:: ComplexSelectorChildren :: Combinator ( _) => true ,
729
- } ) {
805
+ }
806
+
807
+ if is_complex_not_pure ( n) {
730
808
self . errors
731
809
. push ( CssError :: SwcSelectorInModuleNotPure { span : n. span } ) ;
732
810
}
@@ -747,9 +825,12 @@ impl lightningcss::visitor::Visitor<'_> for CssValidator {
747
825
& mut self ,
748
826
selector : & mut lightningcss:: selector:: Selector < ' _ > ,
749
827
) -> Result < ( ) , Self :: Error > {
750
- if selector
751
- . iter_raw_parse_order_from ( 0 )
752
- . all ( |component| match component {
828
+ fn is_selector_problematic ( sel : & lightningcss:: selector:: Selector ) -> bool {
829
+ sel. iter_raw_parse_order_from ( 0 ) . all ( is_problematic)
830
+ }
831
+
832
+ fn is_problematic ( c : & lightningcss:: selector:: Component ) -> bool {
833
+ match c {
753
834
parcel_selectors:: parser:: Component :: ID ( ..)
754
835
| parcel_selectors:: parser:: Component :: Class ( ..) => false ,
755
836
@@ -760,13 +841,19 @@ impl lightningcss::visitor::Visitor<'_> for CssValidator {
760
841
| parcel_selectors:: parser:: Component :: ExplicitUniversalType
761
842
| parcel_selectors:: parser:: Component :: Negation ( ..) => true ,
762
843
844
+ parcel_selectors:: parser:: Component :: Where ( sel) => {
845
+ sel. iter ( ) . all ( is_selector_problematic)
846
+ }
847
+
763
848
parcel_selectors:: parser:: Component :: LocalName ( local) => {
764
849
// Allow html and body. They are not pure selectors but are allowed.
765
850
!matches ! ( & * local. name. 0 , "html" | "body" )
766
851
}
767
852
_ => false ,
768
- } )
769
- {
853
+ }
854
+ }
855
+
856
+ if is_selector_problematic ( selector) {
770
857
self . errors
771
858
. push ( CssError :: LightningCssSelectorInModuleNotPure {
772
859
selector : format ! ( "{selector:?}" ) ,
@@ -1069,6 +1156,18 @@ mod tests {
1069
1156
}" ,
1070
1157
) ;
1071
1158
1159
+ assert_lint_success (
1160
+ ":where(.main > *) {
1161
+ color: red;
1162
+ }" ,
1163
+ ) ;
1164
+
1165
+ assert_lint_success (
1166
+ ":where(.main > *, .root > *) {
1167
+ color: red;
1168
+ }" ,
1169
+ ) ;
1170
+
1072
1171
assert_lint_failure (
1073
1172
"div {
1074
1173
color: red;
@@ -1128,5 +1227,17 @@ mod tests {
1128
1227
--foo: 1;
1129
1228
}" ,
1130
1229
) ;
1230
+
1231
+ assert_lint_failure (
1232
+ ":where(div > *) {
1233
+ color: red;
1234
+ }" ,
1235
+ ) ;
1236
+
1237
+ assert_lint_failure (
1238
+ ":where(div) {
1239
+ color: red;
1240
+ }" ,
1241
+ ) ;
1131
1242
}
1132
1243
}
0 commit comments