1use 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}