refactor!: remove lepty-scoping feature and make its behaviour the default; deprecate scope members; bump MSRV; bump deps

This commit is contained in:
yiffyrusdev 2025-09-12 14:15:47 +05:00 • committed by spushk
commit 995cd31aab
Signed by: spushk
GPG key ID: 7797B761E105E8AA
6 changed files with 284 additions and 374 deletions

View file

@ -16,7 +16,6 @@ regex.optional = true
grass.workspace = true
grass.optional = true
lightningcss.workspace = true
lightningcss.features = ["grid"]
lightningcss.optional = true
blake3.workspace = true
blake3.optional = true
@ -30,7 +29,6 @@ toml.workspace = true
cfg-if.workspace = true
[features]
lepty-scoping = []
scope = ["dep:blake3", "dep:base64", "dep:regex"]
compile = ["dep:lightningcss", "dep:grass"]

View file

@ -217,31 +217,21 @@ impl From<SabryLightTargets> for lightningcss::targets::Browsers {
}
/// \[package.metadata.sabry.sass\].module_name_collision option
#[derive(Deserialize, Clone, Copy)]
#[derive(Default, Deserialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum BehavSassModCollision {
#[default]
Merge,
Error,
}
impl Default for BehavSassModCollision {
fn default() -> Self {
Self::Merge
}
}
/// \[package.metadata.sabry.hash\].collision option
#[derive(Deserialize, Clone, Copy)]
#[derive(Default, Deserialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum BehavHashCollision {
Error,
#[default]
Ignore,
}
impl Default for BehavHashCollision {
fn default() -> Self {
Self::Ignore
}
Error,
}
#[cfg(test)]

View file

@ -1,3 +1,5 @@
use raffia::ast::InterpolableIdent;
/// Convenience wrapper for String-being-a-hash
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ScopeHash(String);
@ -30,7 +32,10 @@ impl ScopeHash {
.iter()
.map(|s| &s.name)
.chain(ids.iter().map(|i| &i.name))
.map(|ident| ident.as_literal())
.map(|ident| match ident {
InterpolableIdent::Literal(lit) => Some(lit),
_ => None,
})
.filter_map(|mbl| mbl.map(|l| l.raw))
.collect::<String>();

View file

@ -1,8 +1,10 @@
use std::fmt::Debug;
use cfg_if::cfg_if;
use hash::ScopeHash;
use raffia::{Span, Spanned};
use raffia::{
ast::{InterpolableIdent, TypeSelector},
Span, Spanned,
};
use crate::syntax::{ostrta::OneSyntaxToRuleThemAll, StylesheetAdapter};
@ -106,7 +108,10 @@ impl<'s> HashedScope<'s> {
let tags = scope.adapter().type_selectors();
let tags = tags
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.map(|c| HashedSelector::from_tag(&hash, c));
// get global selectors
@ -234,29 +239,17 @@ impl HashedSelector {
/// whats this function returns will be usable in HTML with to_hashed_html_def()
pub fn make_hashed_css(value: &ScopedSelector, hash: &ScopeHash) -> String {
match value {
// Class scoping is done with class composition
ScopedSelector::Class(a) => {
// Class scoping is done by composition
format!("{}.{}", hash.as_str(), a.ident)
}
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
ScopedSelector::Id(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
format!("{}.{}", a.ident, hash.as_str())
} else {
format!("{}-{}", hash.as_str(), a.ident)
}
}
}
ScopedSelector::Tag(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
format!("{}.{}", a.ident, hash.as_str())
} else {
format!(".{} {}", hash.as_str(), a.ident)
}
}
// Tag scoping is done by composition
format!("{}.{}", a.ident, hash.as_str())
}
ScopedSelector::Id(a) => {
// ID scoping is done by class composition
format!("{}.{}", a.ident, hash.as_str())
}
ScopedSelector::Glob { raw, .. } => raw.clone(),
}
@ -267,31 +260,15 @@ impl HashedSelector {
/// Cooperates with the [HashedSelector::make_hashed_css], so
/// whats this function returns will be usable in CSS with to_hashed_code()
///
/// Not every hashed selector is presentable for HTML-ish use: like `div`, in that case
/// returns [None]
pub fn make_hashed_html(value: &ScopedSelector, _hash: &ScopeHash) -> Option<String> {
/// Since 0.0.6 all scoping is done via classes, so this function
/// persists for bw-compat for everything except for class.
///
/// Global and Tag selectors are never transformed;
pub fn make_hashed_html(value: &ScopedSelector, hash: &ScopeHash) -> Option<String> {
match value {
// Class scoping is done with class composition
ScopedSelector::Class(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
Some(a.ident.to_string())
} else {
Some(format!("{} {}", _hash.as_str(), a.ident))
}
}
}
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
ScopedSelector::Id(a) => {
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
Some(a.ident.to_string())
} else {
Some(format!("{}-{}", _hash.as_str(), a.ident))
}
}
}
ScopedSelector::Class(a) => Some(format!("{} {}", a.ident, hash.as_str())),
ScopedSelector::Id(id) => Some(id.ident.clone()),
ScopedSelector::Tag(_) => None,
ScopedSelector::Glob { .. } => None,
}
@ -366,10 +343,9 @@ impl ScopedSelector {
/// Construct this from class selector
pub fn from_class(s: &raffia::ast::ClassSelector) -> Self {
let lit = s
.name
.as_literal()
.expect("BUG: class selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name else {
panic!("BUG: class selector was not a literal")
};
Self::Class(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -378,10 +354,9 @@ impl ScopedSelector {
/// Construct this from id selector
pub fn from_id(s: &raffia::ast::IdSelector) -> Self {
let lit = s
.name
.as_literal()
.expect("BUG: id selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name else {
panic!("BUG: id selector was not a literal")
};
Self::Id(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -390,11 +365,9 @@ impl ScopedSelector {
/// Construct this from tagname selector
pub fn from_tag(s: &raffia::ast::TagNameSelector) -> Self {
let lit = s
.name
.name
.as_literal()
.expect("BUG: tag selector is not a literal");
let InterpolableIdent::Literal(lit) = &s.name.name else {
panic!("BUG: tag selector was not a literal")
};
Self::Tag(ArbitrarySelector {
ident: lit.raw.to_string(),
span: lit.span().clone(),
@ -439,7 +412,6 @@ pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String {
mod test {
use std::collections::HashSet;
use cfg_if::cfg_if;
use syn::Ident;
use crate::{
@ -456,33 +428,33 @@ mod test {
".cls1{color:red; &-dark{color: black;} #id1 {color:green;} div {color:blue;}} .cls3#id2{color: black;}";
let hash = ScopeHash::test_init("F2kf8nMs".into());
cfg_if! {
if #[cfg(feature = "lepty-scoping")]{
let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #id1.F2kf8nMs {color:green;} div.F2kf8nMs {color:blue;}} .F2kf8nMs.cls3#id2.F2kf8nMs{color: black;}";
} else {
let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
}
}
//cfg_if! {
//if #[cfg(feature = "lepty-scoping")]{
let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #id1.F2kf8nMs {color:green;} div.F2kf8nMs {color:blue;}} .F2kf8nMs.cls3#id2.F2kf8nMs{color: black;}";
//} else {
// let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
//}
//}
cfg_if! {
if #[cfg(feature = "lepty-scoping")] {
let expect_selector_htmls = HashSet::from([
"cls1".to_string(),
"id1".to_string(),
"cls3".to_string(),
"id2".to_string(),
"".to_string(), // comes from `div` which has no html hashed code, still want to check presense
]);
} else {
let expect_selector_htmls = HashSet::from([
"F2kf8nMs cls1".to_string(),
"F2kf8nMs-id1".to_string(),
"F2kf8nMs cls3".to_string(),
"F2kf8nMs-id2".to_string(),
"".to_string(), // comes from `div` which has no html hashed code, still want to check presense
]);
}
}
// cfg_if! {
// if #[cfg(feature = "lepty-scoping")] {
let expect_selector_htmls = HashSet::from([
"cls1".to_string(),
"id1".to_string(),
"cls3".to_string(),
"id2".to_string(),
"".to_string(), // comes from `div` which has no html hashed code, still want to check presense
]);
// } else {
// let expect_selector_htmls = HashSet::from([
// "F2kf8nMs cls1".to_string(),
// "F2kf8nMs-id1".to_string(),
// "F2kf8nMs cls3".to_string(),
// "F2kf8nMs-id2".to_string(),
// "".to_string(), // comes from `div` which has no html hashed code, still want to check presense
// ]);
// }
//}
let scope = ArbitraryScope::from_source(
OneSyntaxToRuleThemAll::Scss,

View file

@ -1,8 +1,8 @@
use ostrta::OneSyntaxToRuleThemAll;
use raffia::{
ast::{
ClassSelector, CompoundSelector, IdSelector, NestingSelector, PseudoClassSelector,
SimpleSelector, Statement, Stylesheet, TypeSelector,
ClassSelector, ComplexSelectorChild, CompoundSelector, IdSelector, InterpolableIdent,
NestingSelector, PseudoClassSelector, SimpleSelector, Statement, Stylesheet, TypeSelector,
},
ParserBuilder,
};
@ -41,38 +41,49 @@ impl<'s> StylesheetAdapter<'s> {
}
pub fn class_selectors(&self) -> Vec<ClassSelector<'s>> {
self.selectors_by(|c| c.as_class().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Class(sel) => Some(sel),
_ => None,
})
}
pub fn id_selectors(&self) -> Vec<IdSelector<'s>> {
self.selectors_by(|c| c.as_id().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Id(sel) => Some(sel),
_ => None,
})
}
pub fn type_selectors(&self) -> Vec<TypeSelector<'s>> {
self.selectors_by(|c| c.as_type().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Type(sel) => Some(sel),
_ => None,
})
}
pub fn nesting_selectors(&self) -> Vec<NestingSelector<'s>> {
self.selectors_by(|c| c.as_nesting().cloned())
self.selectors_by(|c| match c {
SimpleSelector::Nesting(sel) => Some(sel),
_ => None,
})
}
/// special selector case searching for :g(whatever), :glob(whatever), :global(whatever)
pub fn glob_modified_selectors(&self) -> Vec<PseudoClassSelector<'s>> {
self.selectors_by(|c| {
let cps = match c.as_pseudo_class() {
Some(s) => s.clone(),
let cps = match c {
SimpleSelector::PseudoClass(sel) => sel,
_ => return None,
};
let ident = match cps.name.as_literal().map(|r| r.raw) {
Some(i) => i,
_ => return None,
};
if ident == "global" {
Some(cps)
} else {
None
match &cps.name {
InterpolableIdent::Literal(lit) => match lit.raw {
"g" => Some(cps),
"glob" => Some(cps),
"global" => Some(cps),
_ => None,
},
_ => None,
}
})
}
@ -106,21 +117,27 @@ impl<'s> StylesheetAdapter<'s> {
// 4 - Danik
// 5 - Yiffy
// 8 - Danik
c.filter_map(|s| s.as_qualified_rule().cloned())
.flat_map(|q| {
let thissels = q
.selector
.selectors
.iter()
.flat_map(|sel| &sel.children)
.filter_map(|sc| sc.as_compound_selector())
.cloned();
c.filter_map(|s| match s {
Statement::QualifiedRule(stmt) => Some(stmt),
_ => None,
})
.flat_map(|q| {
let thissels = q
.selector
.selectors
.iter()
.flat_map(|sel| &sel.children)
.filter_map(|sc| match sc {
ComplexSelectorChild::CompoundSelector(sel) => Some(sel),
_ => None,
})
.cloned();
let blockstmt = q.block.statements.iter().cloned();
Self::selectors_of(blockstmt)
.chain(thissels)
.collect::<Vec<_>>()
})
let blockstmt = q.block.statements.iter().cloned();
Self::selectors_of(blockstmt)
.chain(thissels)
.collect::<Vec<_>>()
})
}
}
@ -128,7 +145,10 @@ impl<'s> StylesheetAdapter<'s> {
mod test {
use std::collections::HashSet;
use raffia::Spanned;
use raffia::{
ast::{InterpolableIdent, TypeSelector},
Spanned,
};
use crate::syntax::ostrta::OneSyntaxToRuleThemAll;
@ -191,20 +211,32 @@ div#id2
let classes = adp
.class_selectors()
.iter()
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
.filter_map(|c| match &c.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let ids = adp
.id_selectors()
.iter()
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
.filter_map(|i| match &i.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let tags = adp
.type_selectors()
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.filter_map(|t| match &t.name.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
assert_eq!(expect_classes, classes);
@ -217,20 +249,32 @@ div#id2
let classes = adp
.class_selectors()
.iter()
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
.filter_map(|c| match &c.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let ids = adp
.id_selectors()
.iter()
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
.filter_map(|i| match &i.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
let tags = adp
.type_selectors()
.iter()
.filter_map(|t| t.as_tag_name())
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
.filter_map(|t| match t {
TypeSelector::TagName(tag) => Some(tag),
_ => None,
})
.filter_map(|t| match &t.name.name {
InterpolableIdent::Literal(lit) => Some(lit.raw),
_ => None,
})
.collect::<HashSet<_>>();
assert_eq!(expect_classes, classes);

View file

@ -1,15 +1,10 @@
/// Convenience unification entrypoint
/// to make friends with all the different syntax-consumers
#[derive(Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy)]
pub enum OneSyntaxToRuleThemAll {
Sass,
#[default]
Scss,
}
impl Default for OneSyntaxToRuleThemAll {
fn default() -> Self {
Self::Scss
}
Sass,
}
impl TryFrom<&str> for OneSyntaxToRuleThemAll {