From 4ac316ad5adb2c739f806225552b6e853210934b Mon Sep 17 00:00:00 2001 From: ahabhgk Date: Wed, 8 May 2024 21:13:35 +0800 Subject: [PATCH] feat: add not allowed nested pseudo function warning --- src/dependencies.rs | 86 +++++++++++++------ .../modules_local_by_default_test.rs | 36 ++++++++ tests/test.rs | 10 ++- 3 files changed, 106 insertions(+), 26 deletions(-) diff --git a/src/dependencies.rs b/src/dependencies.rs index a840561..fb3d768 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -173,6 +173,22 @@ impl BalancedStack { } } } + + pub fn inside_mode_function(&self) -> Option { + let mut iter = self.0.iter(); + loop { + if let Some(last) = iter.next_back() { + if matches!( + last.kind, + BalancedItemKind::LocalFn | BalancedItemKind::GlobalFn + ) { + return Some(last.range.start); + } + } else { + return None; + } + } + } } #[derive(Debug)] @@ -473,6 +489,7 @@ pub enum Warning<'s> { ExpectedUrlBefore { range: Range, when: &'s str }, ExpectedLayerBefore { range: Range, when: &'s str }, InconsistentModeResult { range: Range }, + ExpectedNotInside { range: Range, pseudo: &'s str }, } impl Display for Warning<'_> { @@ -498,6 +515,7 @@ impl Display for Warning<'_> { "The 'layer(...)' in '{when}' should be before 'supports(...)'" ), Warning::InconsistentModeResult { .. } => write!(f, "Inconsistent rule global/local (multiple selectors must result in the same mode for the rule)"), + Warning::ExpectedNotInside { pseudo, .. } => write!(f, "A '{pseudo}' is not allowed inside of a ':local()' or ':global()'"), } } } @@ -1132,29 +1150,33 @@ impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDepen return Some(()); }; if let Some(mode_data) = &mut self.mode_data { - let is_function = matches!( + let mut is_function = matches!( last.kind, BalancedItemKind::LocalFn | BalancedItemKind::GlobalFn ); - if is_function - || matches!( - last.kind, - BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass - ) - { - if is_function { - let start = self.consume_back_white_space_and_comments_pos(lexer, start)?; - self.handle_dependency - .handle_dependency(Dependency::Replace { - content: "", - range: Range::new(start, end), - }); - } else { - self.balanced.pop_mode_pseudo_class(mode_data); - let popped = self.balanced.pop_without_moda_data(); - debug_assert!(matches!(popped.unwrap().kind, BalancedItemKind::Other)); - } - return Some(()); + let is_class = matches!( + last.kind, + BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass + ); + if is_class { + self.balanced.pop_mode_pseudo_class(mode_data); + let popped = self.balanced.pop_without_moda_data().unwrap(); + debug_assert!(!matches!( + popped.kind, + BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass + )); + is_function = matches!( + popped.kind, + BalancedItemKind::LocalFn | BalancedItemKind::GlobalFn + ); + } + if is_function { + let start = self.consume_back_white_space_and_comments_pos(lexer, start)?; + self.handle_dependency + .handle_dependency(Dependency::Replace { + content: "", + range: Range::new(start, end), + }); } } if let Scope::InAtImport(ref mut import_data) = self.scope { @@ -1308,13 +1330,16 @@ impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDepen Some(()) } - fn pseudo_function(&mut self, lexer: &mut Lexer, start: Pos, end: Pos) -> Option<()> { + fn pseudo_function(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> { let name = lexer.slice(start, end)?.to_ascii_lowercase(); - self.balanced.push( - BalancedItem::new(&name, start, end), - self.mode_data.as_mut(), - ); if self.mode_data.is_some() && (name == ":global(" || name == ":local(") { + if let Some(inside_start) = self.balanced.inside_mode_function() { + self.handle_warning + .handle_warning(Warning::ExpectedNotInside { + range: Range::new(inside_start, end), + pseudo: lexer.slice(start, end)?, + }); + } lexer.consume_white_space_and_comments()?; self.handle_dependency .handle_dependency(Dependency::Replace { @@ -1322,6 +1347,10 @@ impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDepen range: Range::new(start, lexer.cur_pos()?), }); } + self.balanced.push( + BalancedItem::new(&name, start, end), + self.mode_data.as_mut(), + ); Some(()) } @@ -1331,6 +1360,13 @@ impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDepen }; let name = lexer.slice(start, end)?.to_ascii_lowercase(); if name == ":global" || name == ":local" { + if let Some(inside_start) = self.balanced.inside_mode_function() { + self.handle_warning + .handle_warning(Warning::ExpectedNotInside { + range: Range::new(inside_start, end), + pseudo: lexer.slice(start, end)?, + }); + } self.balanced.push( BalancedItem::new(&name, start, end), self.mode_data.as_mut(), diff --git a/tests/postcss_plugins/modules_local_by_default_test.rs b/tests/postcss_plugins/modules_local_by_default_test.rs index c887f86..6797b09 100644 --- a/tests/postcss_plugins/modules_local_by_default_test.rs +++ b/tests/postcss_plugins/modules_local_by_default_test.rs @@ -687,3 +687,39 @@ fn throw_on_inconsistent_selector_result() { "Inconsistent", ); } + +#[test] +fn throw_on_nested_locals() { + test_with_warning( + ":local(:local(.foo)) {}", + ":local(.foo) {}", + "is not allowed inside", + ); +} + +#[test] +fn throw_on_nested_globals() { + test_with_warning( + ":global(:global(.foo)) {}", + ".foo {}", + "is not allowed inside", + ); +} + +#[test] +fn throw_on_nested_mixed() { + test_with_warning( + ":local(:global(.foo)) {}", + ".foo {}", + "is not allowed inside", + ); +} + +#[test] +fn throw_on_nested_broad_local() { + test_with_warning( + ":global(:local .foo) {}", + ":local(.foo) {}", + "is not allowed inside", + ); +} diff --git a/tests/test.rs b/tests/test.rs index c9632c1..cffb6d8 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -155,7 +155,8 @@ fn assert_warning(input: &str, warning: &Warning, range_content: &str) { | Warning::ExpectedUrl { range, .. } | Warning::ExpectedUrlBefore { range, .. } | Warning::ExpectedLayerBefore { range, .. } - | Warning::InconsistentModeResult { range } => { + | Warning::InconsistentModeResult { range } + | Warning::ExpectedNotInside { range, .. } => { assert_eq!(slice_range(input, range).unwrap(), range_content); } }; @@ -905,6 +906,13 @@ fn css_modules_pseudo_6() { assert_eq!(dependencies.len(), 5); } +#[test] +fn t() { + let input = ":global(:local .foo) {}"; + let (dependencies, warnings) = collect_css_modules_dependencies(input); + dbg!(dependencies, warnings); +} + #[test] fn css_modules_nesting() { let input = indoc! {r#"