Skip to main content

omena_semantic/
css_modules.rs

1//! CSS Modules semantic summaries over parser facts.
2//!
3//! The public V0 payloads in this module describe class definitions,
4//! compositions, exported values, and capability flags that downstream query and
5//! checker layers use without reinterpreting parser-specific fact shapes.
6
7use std::collections::BTreeSet;
8
9use omena_parser::{
10    ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind, ParsedCssModuleComposesFactKind,
11    ParsedCssModuleValueFactKind, ParsedIcssFactKind, ParsedSelectorFactKind, ParsedStyleFacts,
12    StyleDialect, facts_from_cst, parse,
13};
14use serde::Serialize;
15
16use crate::Stylesheet;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct CssModulesSemanticSummaryV0 {
21    pub schema_version: &'static str,
22    pub product: &'static str,
23    pub status: &'static str,
24    pub resolution_scope: &'static str,
25    pub class_export_count: usize,
26    pub class_export_names: Vec<String>,
27    pub composes_edge_seed_count: usize,
28    pub composes_local_edge_count: usize,
29    pub composes_global_edge_count: usize,
30    pub composes_external_edge_count: usize,
31    pub composes_target_names: Vec<String>,
32    pub composes_import_sources: Vec<String>,
33    pub value_edge_seed_count: usize,
34    pub value_import_edge_count: usize,
35    pub value_definition_edge_count: usize,
36    pub value_definition_names: Vec<String>,
37    pub value_reference_names: Vec<String>,
38    pub value_import_sources: Vec<String>,
39    pub icss_edge_seed_count: usize,
40    pub icss_import_edge_count: usize,
41    pub icss_export_edge_count: usize,
42    pub icss_export_names: Vec<String>,
43    pub icss_import_local_names: Vec<String>,
44    pub icss_import_remote_names: Vec<String>,
45    pub icss_import_sources: Vec<String>,
46    pub keyframe_names: Vec<String>,
47    pub animation_reference_names: Vec<String>,
48    pub capabilities: CssModulesSemanticCapabilitiesV0,
49    pub next_priorities: Vec<&'static str>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
53#[serde(rename_all = "camelCase")]
54pub struct CssModulesSemanticCapabilitiesV0 {
55    pub parser_fact_surface_ready: bool,
56    pub per_file_symbol_summary_ready: bool,
57    pub composes_edge_seed_ready: bool,
58    pub value_edge_seed_ready: bool,
59    pub icss_edge_seed_ready: bool,
60    pub animation_edge_seed_ready: bool,
61    pub cross_file_resolution_ready: bool,
62    pub composes_closure_ready: bool,
63    pub value_graph_resolution_ready: bool,
64    pub cycle_detection_ready: bool,
65}
66
67pub fn summarize_css_modules_semantics(sheet: &Stylesheet) -> CssModulesSemanticSummaryV0 {
68    summarize_css_modules_semantics_for_source(sheet.source.as_str(), sheet.language)
69}
70
71pub fn summarize_css_modules_semantics_from_source(
72    style_path: &str,
73    style_source: &str,
74) -> Option<CssModulesSemanticSummaryV0> {
75    let dialect = dialect_for_style_path(style_path)?;
76    Some(summarize_css_modules_semantics_for_source(
77        style_source,
78        dialect,
79    ))
80}
81
82fn summarize_css_modules_semantics_for_source(
83    style_source: &str,
84    dialect: StyleDialect,
85) -> CssModulesSemanticSummaryV0 {
86    let parsed = parse(style_source, dialect);
87    let facts = facts_from_cst(style_source, &parsed);
88    summarize_css_modules_semantics_from_facts(&facts)
89}
90
91pub(crate) fn summarize_css_modules_semantics_from_facts(
92    facts: &ParsedStyleFacts,
93) -> CssModulesSemanticSummaryV0 {
94    let mut class_export_names = BTreeSet::new();
95    let mut composes_target_names = BTreeSet::new();
96    let mut composes_import_sources = BTreeSet::new();
97    let mut composes_local_edge_count = 0usize;
98    let mut composes_global_edge_count = 0usize;
99    let mut composes_external_edge_count = 0usize;
100    let mut value_definition_names = BTreeSet::new();
101    let mut value_reference_names = BTreeSet::new();
102    let mut value_import_sources = BTreeSet::new();
103    let mut icss_export_names = BTreeSet::new();
104    let mut icss_import_local_names = BTreeSet::new();
105    let mut icss_import_remote_names = BTreeSet::new();
106    let mut icss_import_sources = BTreeSet::new();
107    let mut keyframe_names = BTreeSet::new();
108    let mut animation_reference_names = BTreeSet::new();
109
110    for selector in &facts.selectors {
111        if selector.kind == ParsedSelectorFactKind::Class {
112            class_export_names.insert(selector.name.clone());
113        }
114    }
115
116    for composes in &facts.css_module_composes {
117        match composes.kind {
118            ParsedCssModuleComposesFactKind::Target => {
119                composes_target_names.insert(composes.name.clone());
120            }
121            ParsedCssModuleComposesFactKind::ImportSource => {
122                composes_import_sources.insert(composes.name.clone());
123            }
124        }
125    }
126    for edge in &facts.css_module_composes_edges {
127        match edge.kind {
128            ParsedCssModuleComposesEdgeKind::Local => composes_local_edge_count += 1,
129            ParsedCssModuleComposesEdgeKind::Global => composes_global_edge_count += 1,
130            ParsedCssModuleComposesEdgeKind::External => composes_external_edge_count += 1,
131        }
132    }
133
134    for value in &facts.css_module_values {
135        match value.kind {
136            ParsedCssModuleValueFactKind::Definition => {
137                value_definition_names.insert(value.name.clone());
138            }
139            ParsedCssModuleValueFactKind::Reference => {
140                value_reference_names.insert(value.name.clone());
141            }
142            ParsedCssModuleValueFactKind::ImportSource => {
143                value_import_sources.insert(value.name.clone());
144            }
145        }
146    }
147
148    for icss in &facts.icss {
149        match icss.kind {
150            ParsedIcssFactKind::ExportName => {
151                icss_export_names.insert(icss.name.clone());
152            }
153            ParsedIcssFactKind::ImportLocalName => {
154                icss_import_local_names.insert(icss.name.clone());
155            }
156            ParsedIcssFactKind::ImportRemoteName => {
157                icss_import_remote_names.insert(icss.name.clone());
158            }
159            ParsedIcssFactKind::ImportSource => {
160                icss_import_sources.insert(icss.name.clone());
161            }
162        }
163    }
164
165    for animation in &facts.animations {
166        match animation.kind {
167            ParsedAnimationFactKind::KeyframesDeclaration => {
168                keyframe_names.insert(animation.name.clone());
169            }
170            ParsedAnimationFactKind::AnimationNameReference => {
171                animation_reference_names.insert(animation.name.clone());
172            }
173        }
174    }
175
176    let class_export_names: Vec<_> = class_export_names.into_iter().collect();
177    let composes_target_names: Vec<_> = composes_target_names.into_iter().collect();
178    let composes_import_sources: Vec<_> = composes_import_sources.into_iter().collect();
179    let value_definition_names: Vec<_> = value_definition_names.into_iter().collect();
180    let value_reference_names: Vec<_> = value_reference_names.into_iter().collect();
181    let value_import_sources: Vec<_> = value_import_sources.into_iter().collect();
182    let icss_export_names: Vec<_> = icss_export_names.into_iter().collect();
183    let icss_import_local_names: Vec<_> = icss_import_local_names.into_iter().collect();
184    let icss_import_remote_names: Vec<_> = icss_import_remote_names.into_iter().collect();
185    let icss_import_sources: Vec<_> = icss_import_sources.into_iter().collect();
186    let keyframe_names: Vec<_> = keyframe_names.into_iter().collect();
187    let animation_reference_names: Vec<_> = animation_reference_names.into_iter().collect();
188
189    CssModulesSemanticSummaryV0 {
190        schema_version: "0",
191        product: "omena-semantic.css-modules-semantics",
192        status: "parserFactSeed",
193        resolution_scope: "perFileFactSummary",
194        class_export_count: class_export_names.len(),
195        class_export_names,
196        composes_edge_seed_count: composes_local_edge_count
197            + composes_global_edge_count
198            + composes_external_edge_count,
199        composes_local_edge_count,
200        composes_global_edge_count,
201        composes_external_edge_count,
202        composes_target_names,
203        composes_import_sources,
204        value_edge_seed_count: facts.css_module_value_import_edge_count
205            + facts.css_module_value_definition_edge_count,
206        value_import_edge_count: facts.css_module_value_import_edge_count,
207        value_definition_edge_count: facts.css_module_value_definition_edge_count,
208        value_definition_names,
209        value_reference_names,
210        value_import_sources,
211        icss_edge_seed_count: facts.icss_import_edge_count + facts.icss_export_edge_count,
212        icss_import_edge_count: facts.icss_import_edge_count,
213        icss_export_edge_count: facts.icss_export_edge_count,
214        icss_export_names,
215        icss_import_local_names,
216        icss_import_remote_names,
217        icss_import_sources,
218        keyframe_names,
219        animation_reference_names,
220        capabilities: CssModulesSemanticCapabilitiesV0 {
221            parser_fact_surface_ready: true,
222            per_file_symbol_summary_ready: true,
223            composes_edge_seed_ready: true,
224            value_edge_seed_ready: true,
225            icss_edge_seed_ready: true,
226            animation_edge_seed_ready: true,
227            cross_file_resolution_ready: true,
228            composes_closure_ready: true,
229            value_graph_resolution_ready: true,
230            cycle_detection_ready: true,
231        },
232        next_priorities: vec![],
233    }
234}
235
236fn dialect_for_style_path(style_path: &str) -> Option<StyleDialect> {
237    if style_path.ends_with(".module.css") || style_path.ends_with(".css") {
238        Some(StyleDialect::Css)
239    } else if style_path.ends_with(".module.scss") || style_path.ends_with(".scss") {
240        Some(StyleDialect::Scss)
241    } else if style_path.ends_with(".module.sass") || style_path.ends_with(".sass") {
242        Some(StyleDialect::Sass)
243    } else if style_path.ends_with(".module.less") || style_path.ends_with(".less") {
244        Some(StyleDialect::Less)
245    } else {
246        None
247    }
248}