diff --git a/node/src/lib.rs b/node/src/lib.rs index 6fb2ffa9..541ff1e2 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -117,7 +117,8 @@ struct Config { pub css_modules: Option, pub analyze_dependencies: Option, pub pseudo_classes: Option, - pub unused_symbols: Option> + pub unused_symbols: Option>, + pub remove_unused_vars: Option } #[derive(Debug, Deserialize)] @@ -154,7 +155,8 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result o.nesting, None => false }, - css_modules: config.css_modules.unwrap_or(false) + css_modules: config.css_modules.unwrap_or(false), + collect_used_vars: config.remove_unused_vars.unwrap_or(false) })?; stylesheet.minify(MinifyOptions { targets: config.targets, diff --git a/src/declaration.rs b/src/declaration.rs index f5a8d211..ef1c9be1 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -26,6 +26,7 @@ use crate::targets::Browsers; use crate::parser::ParserOptions; use crate::error::{ParserError, PrinterError}; use crate::logical::LogicalProperties; +use std::collections::HashSet; #[derive(Debug, PartialEq)] pub struct DeclarationBlock { @@ -34,12 +35,13 @@ pub struct DeclarationBlock { } impl DeclarationBlock { - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>, options: &ParserOptions) -> Result>> { + pub fn parse<'i, 't>(input: &mut Parser<'i, 't>, options: &ParserOptions, used_vars: &mut Option>) -> Result>> { let mut important_declarations = DeclarationList::new(); let mut declarations = DeclarationList::new(); let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser { important_declarations: &mut important_declarations, declarations: &mut declarations, + used_vars, options }); while let Some(res) = parser.next() { @@ -91,7 +93,8 @@ impl DeclarationBlock { &mut self, handler: &mut DeclarationHandler, important_handler: &mut DeclarationHandler, - logical_properties: &mut LogicalProperties + logical_properties: &mut LogicalProperties, + used_vars: &Option> ) { macro_rules! handle { ($decls: expr, $handler: expr) => { @@ -99,6 +102,13 @@ impl DeclarationBlock { let handled = $handler.handle_property(decl, logical_properties); if !handled { + if let Some(used_vars) = &used_vars { + if let Property::Custom(custom) = &decl { + if custom.name.starts_with("--") && !used_vars.contains(&custom.name) { + continue + } + } + } $handler.decls.push(decl.clone()); } } @@ -118,6 +128,7 @@ impl DeclarationBlock { struct PropertyDeclarationParser<'a> { important_declarations: &'a mut Vec, declarations: &'a mut Vec, + used_vars: &'a mut Option>, options: &'a ParserOptions } @@ -131,7 +142,7 @@ impl<'a, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a> name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, ) -> Result> { - parse_declaration(name, input, &mut self.declarations, &mut self.important_declarations, &self.options) + parse_declaration(name, input, &mut self.declarations, &mut self.important_declarations, self.used_vars, &self.options) } } @@ -147,9 +158,12 @@ pub(crate) fn parse_declaration<'i, 't>( input: &mut cssparser::Parser<'i, 't>, declarations: &mut DeclarationList, important_declarations: &mut DeclarationList, + used_vars: &mut Option>, options: &ParserOptions ) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> { - let property = input.parse_until_before(Delimiter::Bang, |input| Property::parse(name, input, options))?; + let property = input.parse_until_before(Delimiter::Bang, |input| + Property::parse(name, input, options, used_vars) + )?; let important = input.try_parse(|input| { input.expect_delim('!')?; input.expect_ident_matching("important") diff --git a/src/lib.rs b/src/lib.rs index 3c2ad921..f35cceb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,13 @@ mod tests { assert_eq!(res.exports.unwrap(), expected_exports); } + fn unused_vars_test(source: &str, expected: &str) { + let mut stylesheet = StyleSheet::parse("test.css".into(), source, ParserOptions { collect_used_vars: true, ..ParserOptions::default()}).unwrap(); + stylesheet.minify(MinifyOptions::default()); + let res = stylesheet.to_css(PrinterOptions { minify: true, ..PrinterOptions::default() }).unwrap(); + assert_eq!(res.code, expected); + } + macro_rules! map( { $($key:expr => $name:literal $(referenced: $referenced: literal)? $($value:literal $(global: $global: literal)? $(from $from:literal)?)*),* } => { { @@ -9200,4 +9207,49 @@ mod tests { width: device-width; }"#, "@-ms-viewport{width:device-width}"); } + + #[test] + fn test_removed_unused_vars() { + unused_vars_test(r#" + .foo { + --used: blue; + --unused: red; + } + + .bar { + color: var(--used); + } + "#, ".foo{--used:blue}.bar{color:var(--used)}"); + unused_vars_test(r#" + .foo { + --used: blue; + --unused: red; + } + + .bar { + color: var(--unknown, var(--used)); + } + "#, ".foo{--used:blue}.bar{color:var(--unknown, var(--used))}"); + unused_vars_test(r#" + .foo { + --used: blue; + --unused: red; + } + + .bar { + color: yo var(--unknown, yo var(--used)); + } + "#, ".foo{--used:blue}.bar{color:yo var(--unknown, yo var(--used))}"); + unused_vars_test(r#" + .foo { + --used: blue; + --unused: red; + --foo: var(--used); + } + + .bar { + color: var(--foo); + } + "#, ".foo{--used:blue;--foo:var(--used)}.bar{color:var(--foo)}"); + } } diff --git a/src/parser.rs b/src/parser.rs index ee72e43a..ad390228 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,27 +22,30 @@ use crate::rules::{ use crate::values::ident::CustomIdent; use crate::declaration::{DeclarationBlock, DeclarationList, parse_declaration}; use crate::vendor_prefix::VendorPrefix; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::error::ParserError; #[derive(Default)] pub struct ParserOptions { pub nesting: bool, - pub css_modules: bool + pub css_modules: bool, + pub collect_used_vars: bool } /// The parser for the top-level rules in a stylesheet. pub struct TopLevelRuleParser<'a> { default_namespace: Option, namespace_prefixes: HashMap, + used_vars: &'a mut Option>, options: &'a ParserOptions } impl<'a, 'b> TopLevelRuleParser<'a> { - pub fn new(options: &'a ParserOptions) -> TopLevelRuleParser<'a> { + pub fn new(options: &'a ParserOptions, used_vars: &'a mut Option>) -> TopLevelRuleParser<'a> { TopLevelRuleParser { default_namespace: None, namespace_prefixes: HashMap::new(), + used_vars, options } } @@ -51,6 +54,7 @@ impl<'a, 'b> TopLevelRuleParser<'a> { NestedRuleParser { default_namespace: &mut self.default_namespace, namespace_prefixes: &mut self.namespace_prefixes, + used_vars: self.used_vars, options: &self.options } } @@ -203,10 +207,10 @@ impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a> { } } -#[derive(Clone)] struct NestedRuleParser<'a> { default_namespace: &'a Option, namespace_prefixes: &'a HashMap, + used_vars: &'a mut Option>, options: &'a ParserOptions } @@ -215,6 +219,7 @@ impl<'a, 'b> NestedRuleParser<'a> { let nested_parser = NestedRuleParser { default_namespace: self.default_namespace, namespace_prefixes: self.namespace_prefixes, + used_vars: self.used_vars, options: self.options }; @@ -354,7 +359,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a> { AtRulePrelude::CounterStyle(name) => { Ok(CssRule::CounterStyle(CounterStyleRule { name, - declarations: DeclarationBlock::parse(input, self.options)?, + declarations: DeclarationBlock::parse(input, self.options, &mut None)?, loc })) }, @@ -377,12 +382,14 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a> { vendor_prefix, // TODO: parse viewport descriptors rather than properties // https://drafts.csswg.org/css-device-adapt/#viewport-desc - declarations: DeclarationBlock::parse(input, self.options)?, + declarations: DeclarationBlock::parse(input, self.options, &mut None)?, loc })) }, AtRulePrelude::Keyframes(name, vendor_prefix) => { - let iter = RuleListParser::new_for_nested_rule(input, KeyframeListParser); + let iter = RuleListParser::new_for_nested_rule(input, KeyframeListParser { + used_vars: self.used_vars + }); Ok(CssRule::Keyframes(KeyframesRule { name, keyframes: iter.filter_map(Result::ok).collect(), @@ -393,7 +400,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a> { AtRulePrelude::Page(selectors) => { Ok(CssRule::Page(PageRule { selectors, - declarations: DeclarationBlock::parse(input, self.options)?, + declarations: DeclarationBlock::parse(input, self.options, &mut None)?, loc })) }, @@ -438,9 +445,9 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a> { ) -> Result> { let loc = start.source_location(); let (declarations, rules) = if self.options.nesting { - parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.options)? + parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.used_vars, self.options)? } else { - (DeclarationBlock::parse(input, self.options)?, CssRuleList(vec![])) + (DeclarationBlock::parse(input, self.options, self.used_vars)?, CssRuleList(vec![])) }; Ok(CssRule::Style(StyleRule { selectors, @@ -456,6 +463,7 @@ fn parse_declarations_and_nested_rules<'a, 'i, 't>( input: &mut Parser<'i, 't>, default_namespace: &'a Option, namespace_prefixes: &'a HashMap, + used_vars: &'a mut Option>, options: &'a ParserOptions ) -> Result<(DeclarationBlock, CssRuleList), ParseError<'i, ParserError<'i>>> { let mut important_declarations = DeclarationList::new(); @@ -467,7 +475,8 @@ fn parse_declarations_and_nested_rules<'a, 'i, 't>( options, declarations: &mut declarations, important_declarations: &mut important_declarations, - rules: &mut rules + rules: &mut rules, + used_vars }; let mut declaration_parser = DeclarationListParser::new(input, parser); @@ -500,7 +509,8 @@ pub struct StyleRuleParser<'a> { options: &'a ParserOptions, declarations: &'a mut DeclarationList, important_declarations: &'a mut DeclarationList, - rules: &'a mut CssRuleList + rules: &'a mut CssRuleList, + used_vars: &'a mut Option> } /// Parse a declaration within {} block: `color: blue` @@ -517,7 +527,7 @@ impl<'a, 'i> cssparser::DeclarationParser<'i> for StyleRuleParser<'a> { // Declarations cannot come after nested rules. return Err(input.new_custom_error(ParserError::InvalidNesting)) } - parse_declaration(name, input, &mut self.declarations, &mut self.important_declarations, &self.options) + parse_declaration(name, input, &mut self.declarations, &mut self.important_declarations, self.used_vars, &self.options) } } @@ -565,7 +575,7 @@ impl<'a, 'i> AtRuleParser<'i> for StyleRuleParser<'a> { AtRulePrelude::Media(query) => { self.rules.0.push(CssRule::Media(MediaRule { query, - rules: parse_nested_at_rule(input, self.default_namespace, self.namespace_prefixes, self.options)?, + rules: parse_nested_at_rule(input, self.default_namespace, self.namespace_prefixes, self.used_vars, self.options)?, loc })); Ok(()) @@ -573,13 +583,13 @@ impl<'a, 'i> AtRuleParser<'i> for StyleRuleParser<'a> { AtRulePrelude::Supports(condition) => { self.rules.0.push(CssRule::Supports(SupportsRule { condition, - rules: parse_nested_at_rule(input, self.default_namespace, self.namespace_prefixes, self.options)?, + rules: parse_nested_at_rule(input, self.default_namespace, self.namespace_prefixes, self.used_vars, self.options)?, loc })); Ok(()) }, AtRulePrelude::Nest(selectors) => { - let (declarations, rules) = parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.options)?; + let (declarations, rules) = parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.used_vars, self.options)?; self.rules.0.push(CssRule::Nesting(NestingRule { style: StyleRule { selectors, @@ -605,13 +615,14 @@ fn parse_nested_at_rule<'a, 'i, 't>( input: &mut Parser<'i, 't>, default_namespace: &'a Option, namespace_prefixes: &'a HashMap, + used_vars: &'a mut Option>, options: &'a ParserOptions ) -> Result>> { let loc = input.current_source_location(); // Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule. // These act the same way as if they were nested within a `& { ... }` block. - let (declarations, mut rules) = parse_declarations_and_nested_rules(input, default_namespace, namespace_prefixes, options)?; + let (declarations, mut rules) = parse_declarations_and_nested_rules(input, default_namespace, namespace_prefixes, used_vars, options)?; if declarations.declarations.len() > 0 { rules.0.insert(0, CssRule::Style(StyleRule { @@ -651,7 +662,7 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for StyleRuleParser<'a> { input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i, Self::Error>> { let loc = start.source_location(); - let (declarations, rules) = parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.options)?; + let (declarations, rules) = parse_declarations_and_nested_rules(input, self.default_namespace, self.namespace_prefixes, self.used_vars, self.options)?; self.rules.0.push(CssRule::Style(StyleRule { selectors, vendor_prefix: VendorPrefix::empty(), diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 4b9c58e6..eefe3cef 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -4,6 +4,7 @@ use crate::vendor_prefix::VendorPrefix; use crate::targets::Browsers; use crate::prefixes::Feature; use crate::error::ParserError; +use std::collections::HashSet; #[derive(Debug, Clone, PartialEq)] pub struct CustomProperty { @@ -15,8 +16,9 @@ impl CustomProperty { pub fn parse<'i, 't>( name: CowRcStr<'i>, input: &mut Parser<'i, 't>, + used_vars: &mut Option>, ) -> Result>> { - let value = parse_unknown_value(input)?; + let value = parse_unknown_value(input, used_vars)?; Ok(CustomProperty { name: name.as_ref().into(), value @@ -33,9 +35,10 @@ pub struct UnparsedProperty { impl UnparsedProperty { pub fn parse<'i, 't>( property_id: PropertyId, - input: &mut Parser<'i, 't> + input: &mut Parser<'i, 't>, + used_vars: &mut Option> ) -> Result>> { - let value = parse_unknown_value(input)?; + let value = parse_unknown_value(input, used_vars)?; Ok(UnparsedProperty { property_id, value @@ -60,7 +63,10 @@ impl UnparsedProperty { } } -fn parse_unknown_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result>> { +fn parse_unknown_value<'i, 't>( + input: &mut Parser<'i, 't>, + used_vars: &mut Option>, +) -> Result>> { input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { // Need at least one token let before_first = input.position(); @@ -73,8 +79,14 @@ fn parse_unknown_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result { + Ok(token) => { has_two = true; + if let Some(vars) = used_vars { + if is_var(token) { + // Ignore errors. + let _ = parse_var(input, vars); + } + } }, Err(..) => { // If there is only one token, preserve it, even if it is whitespace. @@ -93,3 +105,34 @@ fn parse_unknown_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result bool { + matches!(token, Token::Function(f) if f.eq_ignore_ascii_case("var")) +} + +fn parse_var<'i, 't>( + input: &mut Parser<'i, 't>, + used_vars: &mut HashSet, +) -> Result<(), ParseError<'i, ParserError<'i>>> { + input.parse_nested_block(|input| -> Result<(), ParseError<'i, ParserError<'i>>> { + let name = input.expect_ident()?.as_ref().to_owned(); + used_vars.insert(name); + if input.try_parse(|input| input.expect_comma()).is_ok() { + parse_fallback(input, used_vars)?; + } + Ok(()) + }) +} + +fn parse_fallback<'i, 't>( + input: &mut Parser<'i, 't>, + used_vars: &mut HashSet, +) -> Result<(), ParseError<'i, ParserError<'i>>> { + while let Ok(token) = input.next() { + if is_var(token) { + parse_var(input, used_vars)?; + } + } + Ok(()) +} diff --git a/src/properties/mod.rs b/src/properties/mod.rs index ae503ac4..ee082efa 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -63,6 +63,7 @@ use crate::error::{ParserError, PrinterError}; use crate::logical::LogicalProperty; use crate::targets::Browsers; use crate::prefixes::Feature; +use std::collections::HashSet; macro_rules! define_properties { ( @@ -279,7 +280,7 @@ macro_rules! define_properties { } impl Property { - pub fn parse<'i, 't>(name: CowRcStr<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions) -> Result>> { + pub fn parse<'i, 't>(name: CowRcStr<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions, used_vars: &mut Option>) -> Result>> { let state = input.state(); match name.as_ref() { $( @@ -296,7 +297,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(PropertyId::$property$((<$vp>::None))?, input)?)) + return Ok(Property::Unparsed(UnparsedProperty::parse(PropertyId::$property$((<$vp>::None))?, input, used_vars)?)) } $( @@ -310,7 +311,7 @@ macro_rules! define_properties { } input.reset(&state); - return Ok(Property::Unparsed(UnparsedProperty::parse(PropertyId::$property(prefix), input)?)) + return Ok(Property::Unparsed(UnparsedProperty::parse(PropertyId::$property(prefix), input, used_vars)?)) } )* )+ @@ -318,7 +319,7 @@ macro_rules! define_properties { } input.reset(&state); - return Ok(Property::Custom(CustomProperty::parse(name, input)?)) + return Ok(Property::Custom(CustomProperty::parse(name, input, used_vars)?)) } #[allow(dead_code)] diff --git a/src/rules/font_face.rs b/src/rules/font_face.rs index df3b02d3..69f4b863 100644 --- a/src/rules/font_face.rs +++ b/src/rules/font_face.rs @@ -287,7 +287,7 @@ impl<'i> cssparser::DeclarationParser<'i> for FontFaceDeclarationParser { } input.reset(&state); - return Ok(FontFaceProperty::Custom(CustomProperty::parse(name, input)?)) + return Ok(FontFaceProperty::Custom(CustomProperty::parse(name, input, &mut None)?)) } } diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs index ab30c844..388a88a7 100644 --- a/src/rules/keyframes.rs +++ b/src/rules/keyframes.rs @@ -8,6 +8,7 @@ use crate::values::ident::CustomIdent; use crate::parser::ParserOptions; use crate::error::{ParserError, PrinterError}; use super::MinifyContext; +use std::collections::HashSet; #[derive(Debug, PartialEq)] pub struct KeyframesRule { @@ -20,7 +21,7 @@ pub struct KeyframesRule { impl KeyframesRule { pub(crate) fn minify(&mut self, context: &mut MinifyContext) { for keyframe in &mut self.keyframes { - keyframe.declarations.minify(context.handler, context.important_handler, context.logical_properties) + keyframe.declarations.minify(context.handler, context.important_handler, context.logical_properties, context.used_vars) } } } @@ -142,15 +143,17 @@ impl ToCss for Keyframe { } } -pub(crate) struct KeyframeListParser; +pub(crate) struct KeyframeListParser<'a> { + pub used_vars: &'a mut Option> +} -impl<'a, 'i> AtRuleParser<'i> for KeyframeListParser { +impl<'a, 'i> AtRuleParser<'i> for KeyframeListParser<'a> { type Prelude = (); type AtRule = Keyframe; type Error = ParserError<'i>; } -impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser { +impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> { type Prelude = Vec; type QualifiedRule = Keyframe; type Error = ParserError<'i>; @@ -172,7 +175,7 @@ impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser { let options = ParserOptions::default(); Ok(Keyframe { selectors, - declarations: DeclarationBlock::parse(input, &options)? + declarations: DeclarationBlock::parse(input, &options, self.used_vars)? }) } } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 0d2a71dc..31ace2cb 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -95,7 +95,8 @@ pub(crate) struct MinifyContext<'a> { pub handler: &'a mut DeclarationHandler, pub important_handler: &'a mut DeclarationHandler, pub logical_properties: &'a mut LogicalProperties, - pub unused_symbols: &'a HashSet + pub unused_symbols: &'a HashSet, + pub used_vars: &'a Option> } impl CssRuleList { @@ -164,7 +165,7 @@ impl CssRuleList { // Merge declarations if the selectors are equivalent, and both are compatible with all targets. if style.selectors == last_style_rule.selectors && style.is_compatible(*context.targets) && last_style_rule.is_compatible(*context.targets) && style.rules.0.is_empty() && last_style_rule.rules.0.is_empty() { last_style_rule.declarations.declarations.extend(style.declarations.declarations.drain(..)); - last_style_rule.declarations.minify(context.handler, context.important_handler, context.logical_properties); + last_style_rule.declarations.minify(context.handler, context.important_handler, context.logical_properties, context.used_vars); continue } else if style.declarations == last_style_rule.declarations && style.rules.0.is_empty() && last_style_rule.rules.0.is_empty() { // Append the selectors to the last rule if the declarations are the same, and all selectors are compatible. diff --git a/src/rules/style.rs b/src/rules/style.rs index dec6052e..085a64c0 100644 --- a/src/rules/style.rs +++ b/src/rules/style.rs @@ -35,7 +35,7 @@ impl StyleRule { } } - self.declarations.minify(context.handler, context.important_handler, context.logical_properties); + self.declarations.minify(context.handler, context.important_handler, context.logical_properties, context.used_vars); if !self.rules.0.is_empty() { self.rules.minify(context, unused); diff --git a/src/stylesheet.rs b/src/stylesheet.rs index cc3c9ec7..b4cbfb87 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -18,7 +18,8 @@ pub use crate::printer::PseudoClasses; pub struct StyleSheet { pub filename: String, pub rules: CssRuleList, - options: ParserOptions + options: ParserOptions, + pub used_vars: Option> } #[derive(Default)] @@ -48,14 +49,23 @@ impl StyleSheet { StyleSheet { filename, rules, - options + options, + used_vars: None } } pub fn parse<'i>(filename: String, code: &'i str, options: ParserOptions) -> Result>> { let mut input = ParserInput::new(&code); let mut parser = Parser::new(&mut input); - let rule_list_parser = RuleListParser::new_for_stylesheet(&mut parser, TopLevelRuleParser::new(&options)); + let mut used_vars = if options.collect_used_vars { + Some(HashSet::new()) + } else { + None + }; + let rule_list_parser = RuleListParser::new_for_stylesheet( + &mut parser, + TopLevelRuleParser::new(&options, &mut used_vars) + ); let mut rules = vec![]; for rule in rule_list_parser { @@ -71,7 +81,8 @@ impl StyleSheet { Ok(StyleSheet { filename, rules: CssRuleList(rules), - options + options, + used_vars }) } @@ -84,7 +95,8 @@ impl StyleSheet { handler: &mut handler, important_handler: &mut important_handler, logical_properties: &mut logical_properties, - unused_symbols: &options.unused_symbols + unused_symbols: &options.unused_symbols, + used_vars: &self.used_vars }, false); logical_properties.to_rules(&mut self.rules); } @@ -150,7 +162,7 @@ impl StyleAttribute { let mut parser = Parser::new(&mut input); let options = ParserOptions::default(); Ok(StyleAttribute { - declarations: DeclarationBlock::parse(&mut parser, &options)? + declarations: DeclarationBlock::parse(&mut parser, &options, &mut None)? }) } @@ -158,7 +170,7 @@ impl StyleAttribute { let mut logical_properties = LogicalProperties::new(None); let mut handler = DeclarationHandler::new(options.targets); let mut important_handler = DeclarationHandler::new(options.targets); - self.declarations.minify(&mut handler, &mut important_handler, &mut logical_properties); + self.declarations.minify(&mut handler, &mut important_handler, &mut logical_properties, &mut None); } pub fn to_css(&self, options: PrinterOptions) -> Result {