cnat 0.0.7

Systematically apply certain modifications to classes, class names, used in your frontend codebase.
Documentation
use std::path::PathBuf;

use swc_common::errors::{ColorConfig, Handler};
use swc_common::sync::Lrc;
use swc_common::{FileName, SourceMap};
use swc_css::visit::{Visit, VisitWith};

use swc_css::{ast::Rule, parser::parse_file};

pub struct ClassNamesCollector {
    pub class_names: Vec<cnat::Str>,
}

impl ClassNamesCollector {
    pub fn new() -> Self {
        ClassNamesCollector {
            class_names: vec![],
        }
    }

    pub fn parse(css_file: PathBuf) -> anyhow::Result<Self> {
        let code = std::fs::read_to_string(&css_file)?;

        let options = swc_css::parser::parser::ParserConfig::default();

        let cm: Lrc<SourceMap> = Default::default();
        let filename = FileName::Real(css_file);
        let cssfile = cm.new_source_file(filename.clone(), code);

        let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));

        let mut errors = vec![];
        let c = parse_file::<Vec<Rule>>(&cssfile, None, options, &mut errors).unwrap();

        for e in errors {
            e.to_diagnostics(&handler).emit();
        }

        let mut ccns = ClassNamesCollector::new();

        c.visit_with(&mut ccns);

        Ok(ccns)
    }
}

impl Visit for ClassNamesCollector {
    fn visit_compound_selector(&mut self, n: &swc_css::ast::CompoundSelector) {
        let selectors = &n.subclass_selectors;

        selectors
            .iter()
            .filter_map(|s| match s {
                swc_css::ast::SubclassSelector::Class(selector) => Some(selector),
                _ => None,
            })
            .for_each(|s| {
                if s.text.value.contains(':') {
                    let cn = s.text.value.split(':').last().expect("should have at least one value after split, since empty selectors aren't allowed");
                    self.class_names.push(cn.into());
                } else {
                    self.class_names.push(s.text.value.as_str().into());
                }
            });
    }
}