diff --git a/node/index.d.ts b/node/index.d.ts index 6ff4d5f1..e1a43102 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -1,4 +1,4 @@ -import type {Targets} from './targets'; +import type { Targets } from './targets'; export interface TransformOptions { /** The filename being transformed. Used for error messages and source maps. */ @@ -14,7 +14,7 @@ export interface TransformOptions { /** Whether to enable various draft syntax. */ drafts?: Drafts, /** Whether to compile this file as a CSS module. */ - cssModules?: boolean, + cssModules?: boolean | CSSModulesConfig, /** * Whether to analyze dependencies (e.g. `@import` and `url()`). * When enabled, `@import` rules are removed, and `url()` dependencies @@ -59,10 +59,19 @@ export interface TransformResult { map: Buffer | void, /** CSS module exports, if enabled. */ exports: CSSModuleExports | void, + /** CSS module references, if `dashedIdents` is enabled. */ + references: CSSModuleReferences, /** `@import` and `url()` dependencies, if enabled. */ dependencies: Dependency[] | void } +export interface CSSModulesConfig { + /** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */ + pattern: string, + /** Whether to rename dashed identifiers, e.g. custom properties. */ + dashedIdents: boolean +} + export type CSSModuleExports = { /** Maps exported (i.e. original) names to local names. */ [name: string]: CSSModuleExport @@ -77,6 +86,11 @@ export interface CSSModuleExport { composes: CSSModuleReference[] } +export type CSSModuleReferences = { + /** Maps placeholder names to references. */ + [name: string]: DependencyCSSModuleReference, +}; + export type CSSModuleReference = LocalCSSModuleReference | GlobalCSSModuleReference | DependencyCSSModuleReference; export interface LocalCSSModuleReference { @@ -158,14 +172,14 @@ export interface TransformAttributeOptions { * that can be replaced with the final urls later (after bundling). * Dependencies are returned as part of the result. */ - analyzeDependencies?: boolean + analyzeDependencies?: boolean } export interface TransformAttributeResult { /** The transformed code. */ code: Buffer, /** `@import` and `url()` dependencies, if enabled. */ - dependencies: Dependency[] | void + dependencies: Dependency[] | void } /** diff --git a/node/src/lib.rs b/node/src/lib.rs index 28f985f2..b9d4222f 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -3,7 +3,7 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; use parcel_css::bundler::{BundleErrorKind, Bundler, FileProvider, SourceProvider}; -use parcel_css::css_modules::CssModuleExports; +use parcel_css::css_modules::{CssModuleExports, CssModuleReferences}; use parcel_css::dependencies::Dependency; use parcel_css::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind}; use parcel_css::stylesheet::{ @@ -67,6 +67,7 @@ struct TransformResult { #[serde(with = "serde_bytes")] map: Option>, exports: Option, + references: Option, dependencies: Option>, } @@ -88,6 +89,7 @@ impl TransformResult { }, )?; obj.set_named_property("exports", ctx.env.to_js_value(&self.exports)?)?; + obj.set_named_property("references", ctx.env.to_js_value(&self.references)?)?; obj.set_named_property("dependencies", ctx.env.to_js_value(&self.dependencies)?)?; Ok(obj.into_unknown()) } @@ -194,7 +196,9 @@ enum CssModulesOption { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct CssModulesConfig { - pattern: String, + pattern: Option, + #[serde(default)] + dashed_idents: bool, } #[derive(Debug, Deserialize)] @@ -255,7 +259,10 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result Some(parcel_css::css_modules::Config::default()), CssModulesOption::Bool(false) => None, CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config { - pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(), + pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| { + parcel_css::css_modules::Pattern::parse(pattern).unwrap() + }), + dashed_idents: c.dashed_idents, }), } } else { @@ -296,6 +303,7 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result(fs: &'i FileProvider, config: &BundleConfig) -> Result Some(parcel_css::css_modules::Config::default()), CssModulesOption::Bool(false) => None, CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config { - pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(), + pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| { + parcel_css::css_modules::Pattern::parse(pattern).unwrap() + }), + dashed_idents: c.dashed_idents, }), } } else { @@ -351,6 +362,7 @@ fn compile_bundle<'i>(fs: &'i FileProvider, config: &BundleConfig) -> Result { +pub(crate) struct PropertyHandlerContext<'i, 'o> { pub targets: Option, pub is_important: bool, supports: Vec>, ltr: Vec>, rtl: Vec>, pub context: DeclarationContext, + pub unused_symbols: &'o HashSet, } -impl<'i> PropertyHandlerContext<'i> { - pub fn new(targets: Option) -> Self { +impl<'i, 'o> PropertyHandlerContext<'i, 'o> { + pub fn new(targets: Option, unused_symbols: &'o HashSet) -> Self { PropertyHandlerContext { targets, is_important: false, @@ -43,6 +46,7 @@ impl<'i> PropertyHandlerContext<'i> { ltr: Vec::new(), rtl: Vec::new(), context: DeclarationContext::None, + unused_symbols, } } diff --git a/src/css_modules.rs b/src/css_modules.rs index e728fc58..90afbb87 100644 --- a/src/css_modules.rs +++ b/src/css_modules.rs @@ -9,7 +9,7 @@ //! will be updated accordingly. A map of the original names to compiled (hashed) names will be returned. use crate::error::PrinterErrorKind; -use crate::properties::css_modules::{Composes, ComposesFrom}; +use crate::properties::css_modules::{Composes, Specifier}; use crate::selector::Selectors; use data_encoding::{Encoding, Specification}; use lazy_static::lazy_static; @@ -25,8 +25,11 @@ use std::path::Path; /// Configuration for CSS modules. #[derive(Default, Clone, Debug)] pub struct Config<'i> { - /// The class name pattern to use. Default is `[hash]_[local]`. + /// The name pattern to use when renaming class names and other identifiers. + /// Default is `[hash]_[local]`. pub pattern: Pattern<'i>, + /// Whether to rename dashed identifiers, e.g. custom properties. + pub dashed_idents: bool, } /// A CSS modules class name pattern. @@ -96,8 +99,14 @@ impl<'i> Pattern<'i> { Ok(()) } - fn write_to_string(&self, hash: &str, path: &Path, local: &str) -> Result { - let mut res = String::new(); + #[inline] + fn write_to_string( + &self, + mut res: String, + hash: &str, + path: &Path, + local: &str, + ) -> Result { self.write(hash, path, local, |s| res.write_str(s))?; Ok(res) } @@ -158,6 +167,9 @@ pub struct CssModuleExport { /// A map of exported names to values. pub type CssModuleExports = HashMap; +/// A map of placeholders to references. +pub type CssModuleReferences = HashMap; + lazy_static! { static ref ENCODER: Encoding = { let mut spec = Specification::new(); @@ -173,21 +185,44 @@ pub(crate) struct CssModule<'a, 'b, 'c> { pub path: &'c Path, pub hash: String, pub exports: &'a mut CssModuleExports, + pub references: &'a mut HashMap, } impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> { - pub fn new(config: &'a Config<'b>, filename: &'c str, exports: &'a mut CssModuleExports) -> Self { + pub fn new( + config: &'a Config<'b>, + filename: &'c str, + exports: &'a mut CssModuleExports, + references: &'a mut HashMap, + ) -> Self { Self { config, path: Path::new(filename), hash: hash(filename, matches!(config.pattern.segments[0], Segment::Hash)), exports, + references, } } pub fn add_local(&mut self, exported: &str, local: &str) { self.exports.entry(exported.into()).or_insert_with(|| CssModuleExport { - name: self.config.pattern.write_to_string(&self.hash, &self.path, local).unwrap(), + name: self + .config + .pattern + .write_to_string(String::new(), &self.hash, &self.path, local) + .unwrap(), + composes: vec![], + is_referenced: false, + }); + } + + pub fn add_dashed(&mut self, local: &str) { + self.exports.entry(local.into()).or_insert_with(|| CssModuleExport { + name: self + .config + .pattern + .write_to_string("--".into(), &self.hash, &self.path, &local[2..]) + .unwrap(), composes: vec![], is_referenced: false, }); @@ -200,7 +235,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> { } std::collections::hash_map::Entry::Vacant(entry) => { entry.insert(CssModuleExport { - name: self.config.pattern.write_to_string(&self.hash, &self.path, name).unwrap(), + name: self + .config + .pattern + .write_to_string(String::new(), &self.hash, &self.path, name) + .unwrap(), composes: vec![], is_referenced: true, }); @@ -208,6 +247,45 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> { } } + pub fn reference_dashed(&mut self, name: &str, from: &Option) -> Option { + let (reference, key) = match from { + Some(Specifier::Global) => return Some(name[2..].into()), + Some(Specifier::File(file)) => ( + CssModuleReference::Dependency { + name: name.to_string(), + specifier: file.to_string(), + }, + file.as_ref(), + ), + None => { + // Local export. Mark as used. + match self.exports.entry(name.into()) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().is_referenced = true; + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(CssModuleExport { + name: self + .config + .pattern + .write_to_string("--".into(), &self.hash, &self.path, name) + .unwrap(), + composes: vec![], + is_referenced: true, + }); + } + } + return None; + } + }; + + let hash = hash(&format!("{}_{}_{}", self.hash, name, key), false); + let name = format!("--{}", hash); + + self.references.insert(name.clone(), reference); + Some(hash) + } + pub fn handle_composes( &mut self, selectors: &SelectorList, @@ -223,13 +301,13 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> { name: self .config .pattern - .write_to_string(&self.hash, &self.path, name.0.as_ref()) + .write_to_string(String::new(), &self.hash, &self.path, name.0.as_ref()) .unwrap(), }, - Some(ComposesFrom::Global) => CssModuleReference::Global { + Some(Specifier::Global) => CssModuleReference::Global { name: name.0.as_ref().into(), }, - Some(ComposesFrom::File(file)) => CssModuleReference::Dependency { + Some(Specifier::File(file)) => CssModuleReference::Dependency { name: name.0.to_string(), specifier: file.to_string(), }, diff --git a/src/declaration.rs b/src/declaration.rs index f03ca527..2d669154 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -157,7 +157,7 @@ impl<'i> DeclarationBlock<'i> { &mut self, handler: &mut DeclarationHandler<'i>, important_handler: &mut DeclarationHandler<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) { macro_rules! handle { ($decls: expr, $handler: expr, $important: literal) => { @@ -494,7 +494,17 @@ impl<'i> DeclarationHandler<'i> { } } - pub fn handle_property(&mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { + pub fn handle_property( + &mut self, + property: &Property<'i>, + context: &mut PropertyHandlerContext<'i, '_>, + ) -> bool { + if !context.unused_symbols.is_empty() + && matches!(property, Property::Custom(custom) if context.unused_symbols.contains(custom.name.as_ref())) + { + return true; + } + self.background.handle_property(property, &mut self.decls, context) || self.border.handle_property(property, &mut self.decls, context) || self.outline.handle_property(property, &mut self.decls, context) @@ -522,7 +532,7 @@ impl<'i> DeclarationHandler<'i> { || self.prefix.handle_property(property, &mut self.decls, context) } - pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i>) { + pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) { self.background.finalize(&mut self.decls, context); self.border.finalize(&mut self.decls, context); self.outline.finalize(&mut self.decls, context); diff --git a/src/lib.rs b/src/lib.rs index 321da0c8..74ceffa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub mod vendor_prefix; #[cfg(test)] mod tests { - use crate::css_modules::{CssModuleExport, CssModuleExports, CssModuleReference}; + use crate::css_modules::{CssModuleExport, CssModuleExports, CssModuleReference, CssModuleReferences}; use crate::dependencies::Dependency; use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind, SelectorError}; use crate::properties::custom::Token; @@ -155,6 +155,7 @@ mod tests { source: &'i str, expected: &str, expected_exports: CssModuleExports, + expected_references: CssModuleReferences, config: crate::css_modules::Config<'i>, ) { let mut stylesheet = StyleSheet::parse( @@ -170,6 +171,7 @@ mod tests { let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); assert_eq!(res.exports.unwrap(), expected_exports); + assert_eq!(res.references.unwrap(), expected_references); } fn custom_media_test(source: &str, expected: &str) { @@ -15779,6 +15781,7 @@ mod tests { "circles" => "EgL3uq_circles" referenced: true, "fade" => "EgL3uq_fade" }, + HashMap::new(), Default::default(), ); @@ -15822,6 +15825,7 @@ mod tests { "a" => "EgL3uq_a", "b" => "EgL3uq_b" }, + HashMap::new(), Default::default(), ); @@ -15859,6 +15863,7 @@ mod tests { "grid" => "EgL3uq_grid", "bar" => "EgL3uq_bar" }, + HashMap::new(), Default::default(), ); @@ -15874,6 +15879,7 @@ mod tests { } "#}, map! {}, + HashMap::new(), Default::default(), ); @@ -15907,6 +15913,7 @@ mod tests { map! { "bar" => "EgL3uq_bar" }, + HashMap::new(), Default::default(), ); @@ -15938,6 +15945,7 @@ mod tests { "test" => "EgL3uq_test" "EgL3uq_foo", "foo" => "EgL3uq_foo" }, + HashMap::new(), Default::default(), ); @@ -15966,6 +15974,7 @@ mod tests { "b" => "EgL3uq_b" "EgL3uq_foo", "foo" => "EgL3uq_foo" }, + HashMap::new(), Default::default(), ); @@ -16002,6 +16011,7 @@ mod tests { "foo" => "EgL3uq_foo", "bar" => "EgL3uq_bar" }, + HashMap::new(), Default::default(), ); @@ -16020,6 +16030,7 @@ mod tests { map! { "test" => "EgL3uq_test" "foo" global: true }, + HashMap::new(), Default::default(), ); @@ -16038,6 +16049,7 @@ mod tests { map! { "test" => "EgL3uq_test" "foo" global: true "bar" global: true }, + HashMap::new(), Default::default(), ); @@ -16056,6 +16068,7 @@ mod tests { map! { "test" => "EgL3uq_test" "foo" from "foo.css" }, + HashMap::new(), Default::default(), ); @@ -16074,6 +16087,7 @@ mod tests { map! { "test" => "EgL3uq_test" "foo" from "foo.css" "bar" from "foo.css" }, + HashMap::new(), Default::default(), ); @@ -16103,6 +16117,7 @@ mod tests { "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css", "foo" => "EgL3uq_foo" }, + HashMap::new(), Default::default(), ); @@ -16120,8 +16135,10 @@ mod tests { map! { "foo" => "test-EgL3uq-foo" }, + HashMap::new(), crate::css_modules::Config { pattern: crate::css_modules::Pattern::parse("test-[hash]-[local]").unwrap(), + ..Default::default() }, ); @@ -16143,6 +16160,7 @@ mod tests { ParserOptions { css_modules: Some(crate::css_modules::Config { pattern: crate::css_modules::Pattern::parse("test-[local]-[hash]").unwrap(), + ..Default::default() }), ..ParserOptions::default() }, @@ -16153,6 +16171,107 @@ mod tests { } else { unreachable!() } + + css_modules_test( + r#" + @property --foo { + syntax: ''; + inherits: false; + initial-value: yellow; + } + + .foo { + --foo: red; + color: var(--foo); + } + "#, + indoc! {r#" + @property --foo { + syntax: ""; + inherits: false; + initial-value: #ff0; + } + + .EgL3uq_foo { + --foo: red; + color: var(--foo); + } + "#}, + map! { + "foo" => "EgL3uq_foo" + }, + HashMap::new(), + Default::default(), + ); + + css_modules_test( + r#" + @property --foo { + syntax: ''; + inherits: false; + initial-value: yellow; + } + + @font-palette-values --Cooler { + font-family: Bixa; + base-palette: 1; + override-colors: 1 #7EB7E4; + } + + .foo { + --foo: red; + --bar: green; + color: var(--foo); + font-palette: --Cooler; + } + + .bar { + color: var(--color from "./b.css"); + } + "#, + indoc! {r#" + @property --EgL3uq_foo { + syntax: ""; + inherits: false; + initial-value: #ff0; + } + + @font-palette-values --EgL3uq_Cooler { + font-family: Bixa; + base-palette: 1; + override-colors: 1 #7eb7e4; + } + + .EgL3uq_foo { + --EgL3uq_foo: red; + --EgL3uq_bar: green; + color: var(--EgL3uq_foo); + font-palette: --EgL3uq_Cooler; + } + + .EgL3uq_bar { + color: var(--ma1CsG); + } + "#}, + map! { + "foo" => "EgL3uq_foo", + "--foo" => "--EgL3uq_foo" referenced: true, + "--bar" => "--EgL3uq_bar", + "bar" => "EgL3uq_bar", + "--Cooler" => "--EgL3uq_Cooler" referenced: true + }, + HashMap::from([( + "--ma1CsG".into(), + CssModuleReference::Dependency { + name: "--color".into(), + specifier: "./b.css".into(), + }, + )]), + crate::css_modules::Config { + dashed_idents: true, + ..Default::default() + }, + ); } #[test] @@ -16406,6 +16525,55 @@ mod tests { }) .unwrap(); assert_eq!(res.code, expected); + + let source = r#" + @property --EgL3uq_foo { + syntax: ""; + inherits: false; + initial-value: #ff0; + } + + @font-palette-values --EgL3uq_Cooler { + font-family: Bixa; + base-palette: 1; + override-colors: 1 #7EB7E4; + } + + .EgL3uq_foo { + --EgL3uq_foo: red; + } + + .EgL3uq_bar { + color: green; + } + "#; + + let expected = indoc! {r#" + .EgL3uq_bar { + color: green; + } + "#}; + + let mut stylesheet = StyleSheet::parse( + "test.css", + &source, + ParserOptions { + nesting: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + stylesheet + .minify(MinifyOptions { + unused_symbols: vec!["--EgL3uq_foo", "--EgL3uq_Cooler"] + .iter() + .map(|s| String::from(*s)) + .collect(), + ..MinifyOptions::default() + }) + .unwrap(); + let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); + assert_eq!(res.code, expected); } #[test] diff --git a/src/macros.rs b/src/macros.rs index c8374184..8ef6c410 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -231,7 +231,7 @@ macro_rules! shorthand_handler { } impl<'i> PropertyHandler<'i> for $name$(<$l>)? { - fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { + fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool { match property { $( Property::$prop(val) => { @@ -258,7 +258,7 @@ macro_rules! shorthand_handler { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return } diff --git a/src/printer.rs b/src/printer.rs index b836f061..1c3e74d5 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -223,8 +223,7 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> { /// as appropriate. If the `css_modules` option was enabled, then a hash /// is added, and the mapping is added to the CSS module. pub fn write_ident(&mut self, ident: &str) -> Result<(), PrinterError> { - let css_module = self.css_module.as_ref(); - if let Some(css_module) = css_module { + if let Some(css_module) = &mut self.css_module { let dest = &mut self.dest; let mut first = true; css_module @@ -239,12 +238,36 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> { serialize_name(s, dest) } })?; + + css_module.add_local(&ident, &ident); } else { serialize_identifier(ident, self)?; } - if let Some(css_module) = &mut self.css_module { - css_module.add_local(&ident, &ident); + Ok(()) + } + + pub(crate) fn write_dashed_ident(&mut self, ident: &str, is_declaration: bool) -> Result<(), PrinterError> { + self.write_str("--")?; + + match &mut self.css_module { + Some(css_module) if css_module.config.dashed_idents => { + let dest = &mut self.dest; + css_module + .config + .pattern + .write(&css_module.hash, &css_module.path, &ident[2..], |s| { + self.col += s.len() as u32; + serialize_name(s, dest) + })?; + + if is_declaration { + css_module.add_dashed(ident); + } + } + _ => { + serialize_name(&ident[2..], self)?; + } } Ok(()) diff --git a/src/properties/align.rs b/src/properties/align.rs index a319939e..2b6f4c81 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -930,7 +930,7 @@ impl<'i> PropertyHandler<'i> for AlignHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - _: &mut PropertyHandlerContext<'i>, + _: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -1037,7 +1037,7 @@ impl<'i> PropertyHandler<'i> for AlignHandler { true } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest); } } diff --git a/src/properties/animation.rs b/src/properties/animation.rs index 5b9afb32..293a58b8 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -295,7 +295,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - _: &mut PropertyHandlerContext<'i>, + _: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -379,7 +379,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest); } } diff --git a/src/properties/background.rs b/src/properties/background.rs index 96e08b16..54131334 100644 --- a/src/properties/background.rs +++ b/src/properties/background.rs @@ -776,7 +776,7 @@ impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { macro_rules! background_image { ($val: ident) => { @@ -841,7 +841,7 @@ impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { // If the last declaration is prefixed, pop the last value // so it isn't duplicated when we flush. if self.has_prefix { diff --git a/src/properties/border.rs b/src/properties/border.rs index c8e30878..4305a18a 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -535,7 +535,7 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -687,7 +687,7 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); self.border_image_handler.finalize(dest, context); self.border_radius_handler.finalize(dest, context); @@ -695,7 +695,7 @@ impl<'i> PropertyHandler<'i> for BorderHandler<'i> { } impl<'i> BorderHandler<'i> { - fn flush(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i>) { + fn flush(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } @@ -1221,7 +1221,7 @@ impl<'i> BorderHandler<'i> { &mut self, unparsed: &UnparsedProperty<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) { let logical_supported = context.is_supported(Feature::LogicalBorders); if logical_supported { diff --git a/src/properties/border_image.rs b/src/properties/border_image.rs index 982f847d..ed069d36 100644 --- a/src/properties/border_image.rs +++ b/src/properties/border_image.rs @@ -369,7 +369,7 @@ impl<'i> PropertyHandler<'i> for BorderImageHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; macro_rules! property { @@ -414,7 +414,7 @@ impl<'i> PropertyHandler<'i> for BorderImageHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest); } } diff --git a/src/properties/border_radius.rs b/src/properties/border_radius.rs index 5d048f5e..00a491b0 100644 --- a/src/properties/border_radius.rs +++ b/src/properties/border_radius.rs @@ -117,7 +117,7 @@ impl<'i> PropertyHandler<'i> for BorderRadiusHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -211,13 +211,13 @@ impl<'i> PropertyHandler<'i> for BorderRadiusHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); } } impl<'i> BorderRadiusHandler<'i> { - fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } diff --git a/src/properties/box_shadow.rs b/src/properties/box_shadow.rs index 24fc3392..4262ce68 100644 --- a/src/properties/box_shadow.rs +++ b/src/properties/box_shadow.rs @@ -136,7 +136,7 @@ impl<'i> PropertyHandler<'i> for BoxShadowHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { match property { Property::BoxShadow(box_shadows, prefix) => { @@ -165,7 +165,7 @@ impl<'i> PropertyHandler<'i> for BoxShadowHandler { true } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { if self.box_shadows.is_none() { return; } diff --git a/src/properties/css_modules.rs b/src/properties/css_modules.rs index cb5094f1..4ea7a553 100644 --- a/src/properties/css_modules.rs +++ b/src/properties/css_modules.rs @@ -17,7 +17,7 @@ pub struct Composes<'i> { #[cfg_attr(feature = "serde", serde(borrow))] pub names: CustomIdentList<'i>, /// Where the class names are composed from. - pub from: Option>, + pub from: Option>, /// The source location of the `composes` property. pub loc: Location, } @@ -31,10 +31,10 @@ pub struct Composes<'i> { derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] -pub enum ComposesFrom<'i> { - /// The class name is global. +pub enum Specifier<'i> { + /// The referenced name is global. Global, - /// The class name comes from the specified file. + /// The referenced name comes from the specified file. #[cfg_attr(feature = "serde", serde(borrow))] File(CowArcStr<'i>), } @@ -52,12 +52,7 @@ impl<'i> Parse<'i> for Composes<'i> { } let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() { - if let Ok(file) = input.try_parse(|input| input.expect_string_cloned()) { - Some(ComposesFrom::File(file.into())) - } else { - input.expect_ident_matching("global")?; - Some(ComposesFrom::Global) - } + Some(Specifier::parse(input)?) } else { None }; @@ -98,12 +93,33 @@ impl ToCss for Composes<'_> { if let Some(from) = &self.from { dest.write_str(" from ")?; - match from { - ComposesFrom::Global => dest.write_str("global")?, - ComposesFrom::File(file) => serialize_string(&file, dest)?, - } + from.to_css(dest)?; + } + + Ok(()) + } +} + +impl<'i> Parse<'i> for Specifier<'i> { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + if let Ok(file) = input.try_parse(|input| input.expect_string_cloned()) { + Ok(Specifier::File(file.into())) + } else { + input.expect_ident_matching("global")?; + Ok(Specifier::Global) } + } +} +impl<'i> ToCss for Specifier<'i> { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match self { + Specifier::Global => dest.write_str("global")?, + Specifier::File(file) => serialize_string(&file, dest)?, + } Ok(()) } } diff --git a/src/properties/custom.rs b/src/properties/custom.rs index f97eba1e..a7648d52 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -5,9 +5,11 @@ use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::PropertyId; use crate::rules::supports::SupportsCondition; +use crate::stylesheet::ParserOptions; use crate::targets::Browsers; -use crate::traits::{Parse, ToCss}; +use crate::traits::{Parse, ParseWithOptions, ToCss}; use crate::values::color::{ColorFallbackKind, CssColor}; +use crate::values::ident::DashedIdentReference; use crate::values::length::serialize_dimension; use crate::values::string::CowArcStr; use crate::values::url::Url; @@ -30,8 +32,9 @@ impl<'i> CustomProperty<'i> { pub fn parse<'t>( name: CowArcStr<'i>, input: &mut Parser<'i, 't>, + options: &ParserOptions, ) -> Result>> { - let value = TokenList::parse(input)?; + let value = TokenList::parse(input, options)?; Ok(CustomProperty { name, value }) } } @@ -56,8 +59,9 @@ impl<'i> UnparsedProperty<'i> { pub fn parse<'t>( property_id: PropertyId<'i>, input: &mut Parser<'i, 't>, + options: &ParserOptions, ) -> Result>> { - let value = TokenList::parse(input)?; + let value = TokenList::parse(input, options)?; Ok(UnparsedProperty { property_id, value }) } @@ -100,6 +104,8 @@ pub enum TokenOrValue<'i> { Color(CssColor), /// A parsed CSS url. Url(Url<'i>), + /// A CSS variable reference. + Var(Variable<'i>), } impl<'i> From> for TokenOrValue<'i> { @@ -116,10 +122,13 @@ impl<'i> TokenOrValue<'i> { } impl<'i> TokenList<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + fn parse<'t>( + input: &mut Parser<'i, 't>, + options: &ParserOptions, + ) -> Result>> { input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { let mut tokens = vec![]; - TokenList::parse_into(input, &mut tokens)?; + TokenList::parse_into(input, &mut tokens, options)?; // Slice off leading and trailing whitespace if there are at least two tokens. // If there is only one token, we must preserve it. e.g. `--foo: ;` is valid. @@ -141,6 +150,7 @@ impl<'i> TokenList<'i> { fn parse_into<'t>( input: &mut Parser<'i, 't>, tokens: &mut Vec>, + options: &ParserOptions, ) -> Result<(), ParseError<'i, ParserError<'i>>> { let mut last_is_delim = false; let mut last_is_whitespace = false; @@ -167,9 +177,17 @@ impl<'i> TokenList<'i> { tokens.push(TokenOrValue::Url(Url::parse(input)?)); last_is_delim = false; last_is_whitespace = false; + } else if f == "var" { + let var = input.parse_nested_block(|input| { + let var = Variable::parse(input, options)?; + Ok(TokenOrValue::Var(var)) + })?; + tokens.push(var); + last_is_delim = true; + last_is_whitespace = false; } else { tokens.push(Token::Function(f).into()); - input.parse_nested_block(|input| TokenList::parse_into(input, tokens))?; + input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options))?; tokens.push(Token::CloseParenthesis.into()); last_is_delim = true; // Whitespace is not required after any of these chars. last_is_whitespace = false; @@ -201,7 +219,7 @@ impl<'i> TokenList<'i> { _ => unreachable!(), }; - input.parse_nested_block(|input| TokenList::parse_into(input, tokens))?; + input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options))?; tokens.push(closing_delimiter.into()); last_is_delim = true; // Whitespace is not required after any of these chars. @@ -278,6 +296,16 @@ impl<'i> TokenList<'i> { url.to_css(dest)?; false } + TokenOrValue::Var(var) => { + var.to_css(dest, is_custom_property)?; + if !dest.minify && i != self.0.len() - 1 && !matches!(self.0[i + 1], TokenOrValue::Token(Token::Comma)) { + // Whitespace is removed during parsing, so add it back if we aren't minifying. + dest.write_char(' ')?; + true + } else { + false + } + } TokenOrValue::Token(token) => { match token { Token::Delim(d) => { @@ -694,3 +722,44 @@ impl<'i> TokenList<'i> { res } } + +/// A CSS variable reference. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Variable<'i> { + /// The variable name. + #[cfg_attr(feature = "serde", serde(borrow))] + pub name: DashedIdentReference<'i>, + /// A fallback value in case the variable is not defined. + pub fallback: Option>, +} + +impl<'i> Variable<'i> { + fn parse<'t>( + input: &mut Parser<'i, 't>, + options: &ParserOptions, + ) -> Result>> { + let name = DashedIdentReference::parse_with_options(input, options)?; + + let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() { + Some(TokenList::parse(input, options)?) + } else { + None + }; + + Ok(Variable { name, fallback }) + } + + fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + dest.write_str("var(")?; + self.name.to_css(dest)?; + if let Some(fallback) = &self.fallback { + dest.delim(',', false)?; + fallback.to_css(dest, is_custom_property)?; + } + dest.write_char(')') + } +} diff --git a/src/properties/display.rs b/src/properties/display.rs index c39d042d..86ba12ec 100644 --- a/src/properties/display.rs +++ b/src/properties/display.rs @@ -387,7 +387,7 @@ impl<'i> PropertyHandler<'i> for DisplayHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { if let Property::Display(display) = property { match (&self.display, display) { @@ -431,7 +431,7 @@ impl<'i> PropertyHandler<'i> for DisplayHandler<'i> { false } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { if self.display.is_none() { return; } diff --git a/src/properties/flex.rs b/src/properties/flex.rs index 8583910f..49f28d16 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -491,7 +491,7 @@ impl<'i> PropertyHandler<'i> for FlexHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - _: &mut PropertyHandlerContext<'i>, + _: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -603,7 +603,7 @@ impl<'i> PropertyHandler<'i> for FlexHandler { true } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest); } } diff --git a/src/properties/font.rs b/src/properties/font.rs index 5d55c57a..55d96e00 100644 --- a/src/properties/font.rs +++ b/src/properties/font.rs @@ -793,7 +793,7 @@ impl<'i> PropertyHandler<'i> for FontHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -833,7 +833,7 @@ impl<'i> PropertyHandler<'i> for FontHandler<'i> { true } - fn finalize(&mut self, decls: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, decls: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } diff --git a/src/properties/grid.rs b/src/properties/grid.rs index 9fc0e166..8c8a6607 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -1506,7 +1506,7 @@ impl<'i> PropertyHandler<'i> for GridHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -1559,7 +1559,7 @@ impl<'i> PropertyHandler<'i> for GridHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } diff --git a/src/properties/margin_padding.rs b/src/properties/margin_padding.rs index 5e2cd9b2..f20047fa 100644 --- a/src/properties/margin_padding.rs +++ b/src/properties/margin_padding.rs @@ -177,7 +177,7 @@ macro_rules! side_handler { } impl<'i> PropertyHandler<'i> for $name<'i> { - fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { + fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool { use Property::*; macro_rules! property { @@ -252,13 +252,13 @@ macro_rules! side_handler { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); } } impl<'i> $name<'i> { - fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return } diff --git a/src/properties/masking.rs b/src/properties/masking.rs index 8d9a4683..cb3e085c 100644 --- a/src/properties/masking.rs +++ b/src/properties/masking.rs @@ -560,7 +560,7 @@ impl<'i> PropertyHandler<'i> for MaskHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { macro_rules! maybe_flush { ($prop: ident, $val: expr, $vp: expr) => {{ @@ -704,7 +704,7 @@ impl<'i> PropertyHandler<'i> for MaskHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } @@ -717,7 +717,7 @@ impl<'i> PropertyHandler<'i> for MaskHandler<'i> { } impl<'i> MaskHandler<'i> { - fn flush_mask(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn flush_mask(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { let mut images = std::mem::take(&mut self.images); let mut positions = std::mem::take(&mut self.positions); let mut sizes = std::mem::take(&mut self.sizes); @@ -963,7 +963,7 @@ impl<'i> MaskHandler<'i> { } } - fn flush_mask_border(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn flush_mask_border(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { let mut source = std::mem::take(&mut self.border_source); let mut slice = std::mem::take(&mut self.border_slice); let mut width = std::mem::take(&mut self.border_width); diff --git a/src/properties/mod.rs b/src/properties/mod.rs index d3490af5..8288c1b3 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -127,12 +127,12 @@ use crate::parser::ParserOptions; use crate::prefixes::Feature; use crate::printer::{Printer, PrinterOptions}; use crate::targets::Browsers; -use crate::traits::{Parse, Shorthand, ToCss}; +use crate::traits::{Parse, ParseWithOptions, Shorthand, ToCss}; use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::string::CowArcStr; use crate::values::{ - alpha::*, color::*, easing::EasingFunction, ident::DashedIdent, image::*, length::*, position::*, rect::*, - shape::FillRule, size::Size2D, time::Time, + alpha::*, color::*, easing::EasingFunction, ident::DashedIdentReference, image::*, length::*, position::*, + rect::*, shape::FillRule, size::Size2D, time::Time, }; use crate::vendor_prefix::VendorPrefix; use align::*; @@ -168,7 +168,7 @@ macro_rules! define_properties { ( $( $(#[$meta: meta])* - $name: literal: $property: ident($type: ty $(, $vp: ty)?) $( / $prefix: ident )* $( unprefixed: $unprefixed: literal )? $( shorthand: $shorthand: literal )? $( [ logical_group: $logical_group: ident, category: $logical_category: ident ] )? $( if $condition: ident )?, + $name: literal: $property: ident($type: ty $(, $vp: ty)?) $( / $prefix: ident )* $( unprefixed: $unprefixed: literal )? $( options: $options: literal )? $( shorthand: $shorthand: literal )? $( [ logical_group: $logical_group: ident, category: $logical_category: ident ] )? $( if $condition: ident )?, )+ ) => { /// A CSS property id. @@ -548,14 +548,14 @@ macro_rules! define_properties { $( $(#[$meta])* PropertyId::$property$((vp_name!($vp, prefix)))? $(if options.$condition.is_some())? => { - if let Ok(c) = <$type>::parse(input) { + if let Ok(c) = <$type>::parse_with_options(input, options) { if input.expect_exhausted().is_ok() { return Ok(Property::$property(c $(, vp_name!($vp, prefix))?)) } } }, )+ - PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input)?)), + PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input, options)?)), _ => {} }; @@ -564,7 +564,7 @@ macro_rules! define_properties { // and stored as an enum rather than a string. This lets property handlers more easily deal with it. // Ideally we'd only do this if var() or env() references were seen, but err on the safe side for now. input.reset(&state); - return Ok(Property::Unparsed(UnparsedProperty::parse(property_id, input)?)) + return Ok(Property::Unparsed(UnparsedProperty::parse(property_id, input, options)?)) } /// Returns the property id for this property. @@ -661,7 +661,12 @@ macro_rules! define_properties { Unparsed(unparsed) => (unparsed.property_id.name(), unparsed.property_id.prefix()), Custom(custom) => { // Ensure custom property names are escaped. - serialize_name(custom.name.as_ref(), dest)?; + let name = custom.name.as_ref(); + if name.starts_with("--") { + dest.write_dashed_ident(&name, true)?; + } else { + serialize_name(&name, dest)?; + } dest.delim(':', false)?; self.value_to_css(dest)?; write_important!(); @@ -1005,7 +1010,7 @@ define_properties! { "line-height": LineHeight(LineHeight), "font": Font(Font<'i>) shorthand: true, "vertical-align": VerticalAlign(VerticalAlign), - "font-palette": FontPalette(DashedIdent<'i>), + "font-palette": FontPalette(DashedIdentReference<'i>), "transition-property": TransitionProperty(SmallVec<[PropertyId<'i>; 1]>, VendorPrefix) / WebKit / Moz / Ms, "transition-duration": TransitionDuration(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / Ms, diff --git a/src/properties/overflow.rs b/src/properties/overflow.rs index d9e005a7..a0b1d7d2 100644 --- a/src/properties/overflow.rs +++ b/src/properties/overflow.rs @@ -91,7 +91,7 @@ impl<'i> PropertyHandler<'i> for OverflowHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -117,7 +117,7 @@ impl<'i> PropertyHandler<'i> for OverflowHandler { true } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { if self.x.is_none() && self.y.is_none() { return; } diff --git a/src/properties/position.rs b/src/properties/position.rs index b9f8bcb8..73ace120 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -125,7 +125,7 @@ impl<'i> PropertyHandler<'i> for PositionHandler { &mut self, property: &Property<'i>, _: &mut DeclarationList<'i>, - _: &mut PropertyHandlerContext<'i>, + _: &mut PropertyHandlerContext<'i, '_>, ) -> bool { if let Property::Position(position) = property { if let (Some(Position::Sticky(cur)), Position::Sticky(new)) = (&mut self.position, position) { @@ -140,7 +140,7 @@ impl<'i> PropertyHandler<'i> for PositionHandler { false } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { if self.position.is_none() { return; } diff --git a/src/properties/prefix_handler.rs b/src/properties/prefix_handler.rs index 2fc1dd2d..96c3e08b 100644 --- a/src/properties/prefix_handler.rs +++ b/src/properties/prefix_handler.rs @@ -116,7 +116,7 @@ macro_rules! define_fallbacks { } impl<'i> PropertyHandler<'i> for FallbackHandler { - fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { + fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool { match property { $( Property::$name(val $(, mut $p)?) => { diff --git a/src/properties/size.rs b/src/properties/size.rs index f6d4d8ad..1732166f 100644 --- a/src/properties/size.rs +++ b/src/properties/size.rs @@ -173,7 +173,7 @@ impl<'i> PropertyHandler<'i> for SizeHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { let logical_supported = context.is_supported(Feature::LogicalSize); @@ -239,5 +239,5 @@ impl<'i> PropertyHandler<'i> for SizeHandler { true } - fn finalize(&mut self, _: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) {} + fn finalize(&mut self, _: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) {} } diff --git a/src/properties/text.rs b/src/properties/text.rs index bccbddb3..86d88d9d 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -950,7 +950,7 @@ impl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -1047,7 +1047,7 @@ impl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } diff --git a/src/properties/transform.rs b/src/properties/transform.rs index 914f2730..f98dccc5 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1650,7 +1650,7 @@ impl<'i> PropertyHandler<'i> for TransformHandler { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - _: &mut PropertyHandlerContext<'i>, + _: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -1711,7 +1711,7 @@ impl<'i> PropertyHandler<'i> for TransformHandler { true } - fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest); } } diff --git a/src/properties/transition.rs b/src/properties/transition.rs index b07c9917..bc34881d 100644 --- a/src/properties/transition.rs +++ b/src/properties/transition.rs @@ -131,7 +131,7 @@ impl<'i> PropertyHandler<'i> for TransitionHandler<'i> { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { use Property::*; @@ -210,13 +210,13 @@ impl<'i> PropertyHandler<'i> for TransitionHandler<'i> { true } - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); } } impl<'i> TransitionHandler<'i> { - fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>) { + fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return; } diff --git a/src/rules/font_face.rs b/src/rules/font_face.rs index 6aa120b9..492f65bf 100644 --- a/src/rules/font_face.rs +++ b/src/rules/font_face.rs @@ -469,7 +469,11 @@ impl<'i> cssparser::DeclarationParser<'i> for FontFaceDeclarationParser { } input.reset(&state); - return Ok(FontFaceProperty::Custom(CustomProperty::parse(name.into(), input)?)); + return Ok(FontFaceProperty::Custom(CustomProperty::parse( + name.into(), + input, + &Default::default(), + )?)); } } diff --git a/src/rules/font_palette_values.rs b/src/rules/font_palette_values.rs index bb05268f..5d15383d 100644 --- a/src/rules/font_palette_values.rs +++ b/src/rules/font_palette_values.rs @@ -116,6 +116,7 @@ impl<'i> cssparser::DeclarationParser<'i> for FontPaletteValuesDeclarationParser return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse( name.into(), input, + &Default::default(), )?)); } } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 55b0cd44..f7cd48a1 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -232,7 +232,7 @@ pub(crate) struct MinifyContext<'a, 'i> { pub targets: &'a Option, pub handler: &'a mut DeclarationHandler<'i>, pub important_handler: &'a mut DeclarationHandler<'i>, - pub handler_context: &'a mut PropertyHandlerContext<'i>, + pub handler_context: &'a mut PropertyHandlerContext<'i, 'a>, pub unused_symbols: &'a HashSet, pub custom_media: Option, CustomMediaRule<'i>>>, } @@ -391,6 +391,10 @@ impl<'i> CssRuleList<'i> { } } CssRule::FontPaletteValues(f) => { + if context.unused_symbols.contains(f.name.0.as_ref()) { + continue; + } + f.minify(context, parent_is_unused); if let Some(targets) = context.targets { @@ -400,6 +404,11 @@ impl<'i> CssRuleList<'i> { continue; } } + CssRule::Property(property) => { + if context.unused_symbols.contains(property.name.0.as_ref()) { + continue; + } + } _ => {} } diff --git a/src/rules/property.rs b/src/rules/property.rs index a8fd19a1..e7342a2e 100644 --- a/src/rules/property.rs +++ b/src/rules/property.rs @@ -18,15 +18,15 @@ use cssparser::*; pub struct PropertyRule<'i> { /// The name of the custom property to declare. #[cfg_attr(feature = "serde", serde(borrow))] - name: DashedIdent<'i>, + pub name: DashedIdent<'i>, /// A syntax string to specify the grammar for the custom property. - syntax: SyntaxString, + pub syntax: SyntaxString, /// Whether the custom property is inherited. - inherits: bool, + pub inherits: bool, /// An optional initial value for the custom property. - initial_value: Option>, + pub initial_value: Option>, /// The location of the rule in the source file. - loc: Location, + pub loc: Location, } impl<'i> PropertyRule<'i> { diff --git a/src/stylesheet.rs b/src/stylesheet.rs index b0fc2dba..cd222841 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -5,7 +5,7 @@ use crate::compat::Feature; use crate::context::{DeclarationContext, PropertyHandlerContext}; -use crate::css_modules::{CssModule, CssModuleExports}; +use crate::css_modules::{CssModule, CssModuleExports, CssModuleReferences}; use crate::declaration::{DeclarationBlock, DeclarationHandler}; use crate::dependencies::Dependency; use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind}; @@ -89,6 +89,9 @@ pub struct ToCssResult { /// A map of CSS module exports, if the `css_modules` option was /// enabled during parsing. pub exports: Option, + /// A map of CSS module references, if the `css_modules` config + /// had `dashed_idents` enabled. + pub references: Option, /// A list of dependencies (e.g. `@import` or `url()`) found in /// the style sheet, if the `analyze_dependencies` option is enabled. pub dependencies: Option>, @@ -131,7 +134,7 @@ impl<'i, 'o> StyleSheet<'i, 'o> { /// Minify and transform the style sheet for the provided browser targets. pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error> { - let mut context = PropertyHandlerContext::new(options.targets); + let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols); let mut handler = DeclarationHandler::new(options.targets); let mut important_handler = DeclarationHandler::new(options.targets); @@ -182,7 +185,13 @@ impl<'i, 'o> StyleSheet<'i, 'o> { if let Some(config) = &self.options.css_modules { let mut exports = HashMap::new(); - printer.css_module = Some(CssModule::new(config, printer.filename(), &mut exports)); + let mut references = HashMap::new(); + printer.css_module = Some(CssModule::new( + config, + printer.filename(), + &mut exports, + &mut references, + )); self.rules.to_css(&mut printer)?; printer.newline()?; @@ -191,6 +200,7 @@ impl<'i, 'o> StyleSheet<'i, 'o> { dependencies: printer.dependencies, code: dest, exports: Some(exports), + references: Some(references), }) } else { self.rules.to_css(&mut printer)?; @@ -199,6 +209,7 @@ impl<'i, 'o> StyleSheet<'i, 'o> { dependencies: printer.dependencies, code: dest, exports: None, + references: None, }) } } @@ -246,7 +257,7 @@ impl<'i> StyleAttribute<'i> { /// Minify and transform the style attribute for the provided browser targets. pub fn minify(&mut self, options: MinifyOptions) { - let mut context = PropertyHandlerContext::new(options.targets); + let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols); let mut handler = DeclarationHandler::new(options.targets); let mut important_handler = DeclarationHandler::new(options.targets); context.context = DeclarationContext::StyleAttribute; @@ -270,6 +281,7 @@ impl<'i> StyleAttribute<'i> { dependencies: printer.dependencies, code: dest, exports: None, + references: None, }) } } diff --git a/src/traits.rs b/src/traits.rs index e276fd9b..24d90bd4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,7 +5,7 @@ use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::properties::{Property, PropertyId}; -use crate::stylesheet::PrinterOptions; +use crate::stylesheet::{ParserOptions, PrinterOptions}; use crate::targets::Browsers; use crate::vendor_prefix::VendorPrefix; use cssparser::*; @@ -27,6 +27,23 @@ pub trait Parse<'i>: Sized { } } +pub(crate) trait ParseWithOptions<'i>: Sized { + fn parse_with_options<'t>( + input: &mut Parser<'i, 't>, + options: &ParserOptions, + ) -> Result>>; +} + +impl<'i, T: Parse<'i>> ParseWithOptions<'i> for T { + #[inline] + fn parse_with_options<'t>( + input: &mut Parser<'i, 't>, + _options: &ParserOptions, + ) -> Result>> { + T::parse(input) + } +} + /// Trait for things the can serialize themselves in CSS syntax. pub trait ToCss { /// Serialize `self` in CSS syntax, writing to `dest`. @@ -63,9 +80,9 @@ pub(crate) trait PropertyHandler<'i>: Sized { &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, - context: &mut PropertyHandlerContext<'i>, + context: &mut PropertyHandlerContext<'i, '_>, ) -> bool; - fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>); + fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>); } pub(crate) mod private { diff --git a/src/values/ident.rs b/src/values/ident.rs index edb5c2f2..e31a51dc 100644 --- a/src/values/ident.rs +++ b/src/values/ident.rs @@ -2,7 +2,8 @@ use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; -use crate::traits::{Parse, ToCss}; +use crate::properties::css_modules::Specifier; +use crate::traits::{Parse, ParseWithOptions, ToCss}; use crate::values::string::CowArcStr; use cssparser::*; use smallvec::SmallVec; @@ -45,7 +46,7 @@ impl<'i> ToCss for CustomIdent<'i> { /// A list of CSS [``](https://www.w3.org/TR/css-values-4/#custom-idents) values. pub type CustomIdentList<'i> = SmallVec<[CustomIdent<'i>; 1]>; -/// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents). +/// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) declaration. /// /// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined. /// Author defined idents must start with two dash characters ("--") or parsing will fail. @@ -70,6 +71,66 @@ impl<'i> ToCss for DashedIdent<'i> { where W: std::fmt::Write, { - dest.write_ident(&self.0) + dest.write_dashed_ident(&self.0, true) + } +} + +/// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) reference. +/// +/// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined. +/// Author defined idents must start with two dash characters ("--") or parsing will fail. +/// +/// In CSS modules, when the `dashed_idents` option is enabled, the identifier may be followed by the +/// `from` keyword and an argument indicating where the referenced identifier is declared (e.g. a filename). +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DashedIdentReference<'i> { + /// The referenced identifier. + #[cfg_attr(feature = "serde", serde(borrow))] + pub ident: DashedIdent<'i>, + /// CSS modules extension: the filename where the variable is defined. + /// Only enabled when the CSS modules `dashed_idents` option is turned on. + pub from: Option>, +} + +impl<'i> ParseWithOptions<'i> for DashedIdentReference<'i> { + fn parse_with_options<'t>( + input: &mut Parser<'i, 't>, + options: &crate::stylesheet::ParserOptions, + ) -> Result>> { + let ident = DashedIdent::parse(input)?; + + let from = match &options.css_modules { + Some(config) if config.dashed_idents => { + if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() { + Some(Specifier::parse(input)?) + } else { + None + } + } + _ => None, + }; + + Ok(DashedIdentReference { ident, from }) + } +} + +impl<'i> ToCss for DashedIdentReference<'i> { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match &mut dest.css_module { + Some(css_module) if css_module.config.dashed_idents => { + if let Some(name) = css_module.reference_dashed(&self.ident.0, &self.from) { + dest.write_str("--")?; + serialize_name(&name, dest)?; + return Ok(()); + } + } + _ => {} + } + + dest.write_dashed_ident(&self.ident.0, false) } }