1mod animations;
7mod at_rules;
8mod css_modules;
9mod icss;
10mod sass;
11mod selectors;
12mod variables;
13
14use cstree::syntax::SyntaxNode;
15use omena_syntax::{StyleDialect, SyntaxKind};
16
17use crate::{DialectExtension, ParseResult, Parser, Token, tokenize};
18
19pub(crate) use animations::collect_animation_facts_from_cst;
20pub use animations::{ParsedAnimationFact, ParsedAnimationFactKind};
21pub use at_rules::ParsedAtRuleFact;
22pub(crate) use at_rules::collect_at_rule_facts_from_cst;
23pub use css_modules::{
24 ParsedCssModuleComposesEdgeFact, ParsedCssModuleComposesEdgeKind, ParsedCssModuleComposesFact,
25 ParsedCssModuleComposesFactKind, ParsedCssModuleValueDefinitionEdgeFact,
26 ParsedCssModuleValueFact, ParsedCssModuleValueFactKind, ParsedCssModuleValueImportEdgeFact,
27};
28pub(crate) use css_modules::{
29 collect_css_module_composes_edge_facts_from_cst, collect_css_module_composes_facts_from_cst,
30 collect_css_module_value_definition_edge_facts_from_cst,
31 collect_css_module_value_definition_edge_names, collect_css_module_value_facts_from_cst,
32 collect_css_module_value_import_edge_facts_from_cst,
33 css_module_value_reference_token_can_be_name, css_module_value_source_name,
34 css_module_value_statement_end, declaration_colon_index,
35};
36pub use icss::{
37 ParsedIcssExportEdgeFact, ParsedIcssFact, ParsedIcssFactKind, ParsedIcssImportEdgeFact,
38 collect_icss_export_values_from_cst,
39};
40pub(crate) use icss::{
41 collect_icss_export_edge_facts_from_cst, collect_icss_facts_from_cst,
42 collect_icss_import_edge_facts_from_cst,
43};
44pub use sass::{
45 ParsedExtendTargetFact, ParsedExtendTargetFactKind, ParsedSassCallableParameterFact,
46 ParsedSassCallableSignatureFact, ParsedSassIncludeFact, ParsedSassModuleEdgeFact,
47 ParsedSassModuleEdgeFactKind, ParsedSassPlaceholderDefinitionFact, ParsedSassSymbolFact,
48 ParsedSassSymbolFactKind,
49};
50pub(crate) use sass::{
51 collect_extend_target_facts_from_cst, collect_sass_include_facts_from_cst,
52 collect_sass_module_edge_facts_from_cst, collect_sass_placeholder_definition_facts_from_cst,
53 collect_sass_symbol_facts_from_cst,
54};
55pub use selectors::{ParsedSelectorFact, ParsedSelectorFactKind};
56pub(crate) use selectors::{
57 SelectorBranch, collect_class_selector_names_from_header, collect_selector_facts_from_cst,
58 css_module_block_scope_marker_in_header, css_module_header_is_global_only,
59 resolve_selector_header, split_selector_groups,
60};
61pub use variables::{ParsedVariableFact, ParsedVariableFactKind};
62pub(crate) use variables::{collect_variable_facts_from_cst, scss_variable_token_is_declaration};
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct ParsedStyleFacts {
66 pub product: &'static str,
67 pub dialect: StyleDialect,
68 pub selector_count: usize,
69 pub selectors: Vec<ParsedSelectorFact>,
70 pub variable_count: usize,
71 pub variables: Vec<ParsedVariableFact>,
72 pub sass_symbol_count: usize,
73 pub sass_symbols: Vec<ParsedSassSymbolFact>,
74 pub sass_include_count: usize,
75 pub sass_includes: Vec<ParsedSassIncludeFact>,
76 pub sass_module_edge_count: usize,
77 pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
78 pub sass_placeholder_definition_count: usize,
79 pub sass_placeholder_definitions: Vec<ParsedSassPlaceholderDefinitionFact>,
80 pub extend_target_count: usize,
81 pub extend_targets: Vec<ParsedExtendTargetFact>,
82 pub animation_count: usize,
83 pub animations: Vec<ParsedAnimationFact>,
84 pub css_module_value_count: usize,
85 pub css_module_values: Vec<ParsedCssModuleValueFact>,
86 pub css_module_value_import_edge_count: usize,
87 pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
88 pub css_module_value_definition_edge_count: usize,
89 pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
90 pub css_module_composes_count: usize,
91 pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
92 pub css_module_composes_edge_count: usize,
93 pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
94 pub icss_count: usize,
95 pub icss: Vec<ParsedIcssFact>,
96 pub icss_import_edge_count: usize,
97 pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
98 pub icss_export_edge_count: usize,
99 pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
100 pub at_rule_count: usize,
101 pub at_rules: Vec<ParsedAtRuleFact>,
102 pub error_count: usize,
103}
104
105struct ProductFacts(ParsedStyleFacts);
106
107impl From<ParsedStyleFacts> for ProductFacts {
108 fn from(mut projected: ParsedStyleFacts) -> Self {
109 if !matches!(projected.dialect, StyleDialect::Scss | StyleDialect::Sass) {
110 clear_fact_category(
111 &mut projected.sass_symbol_count,
112 &mut projected.sass_symbols,
113 );
114 clear_fact_category(
115 &mut projected.sass_module_edge_count,
116 &mut projected.sass_module_edges,
117 );
118 clear_fact_category(
119 &mut projected.sass_placeholder_definition_count,
120 &mut projected.sass_placeholder_definitions,
121 );
122 }
123
124 clear_fact_category(
125 &mut projected.sass_include_count,
126 &mut projected.sass_includes,
127 );
128 clear_fact_category(
129 &mut projected.extend_target_count,
130 &mut projected.extend_targets,
131 );
132 clear_fact_category(&mut projected.icss_count, &mut projected.icss);
133 clear_fact_category(
134 &mut projected.icss_import_edge_count,
135 &mut projected.icss_import_edges,
136 );
137 clear_fact_category(
138 &mut projected.icss_export_edge_count,
139 &mut projected.icss_export_edges,
140 );
141 clear_fact_category(&mut projected.at_rule_count, &mut projected.at_rules);
142
143 Self(projected)
144 }
145}
146
147impl From<ProductFacts> for ParsedStyleFacts {
148 fn from(facts: ProductFacts) -> Self {
149 facts.0
150 }
151}
152
153fn clear_fact_category<T>(count: &mut usize, facts: &mut Vec<T>) {
154 *count = 0;
155 facts.clear();
156}
157
158pub fn collect_style_facts_with_extension(
159 text: &str,
160 extension: &impl DialectExtension,
161) -> ParsedStyleFacts {
162 let (tokens, lex_errors) = tokenize(text, extension);
163 let token_count = tokens.len();
164 let mut parser = Parser::new(tokens.clone(), lex_errors, extension.dialect());
165 crate::record_omena_parser_parse_materialization(token_count);
166 let (green, interner) = parser.parse();
167 let errors = parser.into_errors();
168 let parsed = ParseResult::new(green, interner, errors, token_count, extension.dialect());
169 facts_from_cst(text, &parsed)
170}
171
172pub fn facts_from_cst(text: &str, parsed: &ParseResult) -> ParsedStyleFacts {
173 let selectors = collect_selector_facts_from_cst(text, parsed);
174 let variables = collect_variable_facts_from_cst(text, parsed);
175 let sass_symbols = collect_sass_symbol_facts_from_cst(text, parsed);
176 let sass_includes = collect_sass_include_facts_from_cst(text, parsed);
177 let sass_module_edges = collect_sass_module_edge_facts_from_cst(text, parsed);
178 let has_placeholder_definition = selectors
179 .iter()
180 .any(|selector| selector.kind == ParsedSelectorFactKind::Placeholder);
181 let sass_placeholder_definitions = if has_placeholder_definition {
182 collect_sass_placeholder_definition_facts_from_cst(text, parsed)
183 } else {
184 Vec::new()
185 };
186 let extend_targets = collect_extend_target_facts_from_cst(text, parsed);
187 let animations = collect_animation_facts_from_cst(text, parsed);
188 let css_module_values = collect_css_module_value_facts_from_cst(text, parsed);
189 let css_module_value_import_edges =
190 collect_css_module_value_import_edge_facts_from_cst(text, parsed);
191 let css_module_value_definition_edges =
192 collect_css_module_value_definition_edge_facts_from_cst(text, parsed);
193 let css_module_composes = collect_css_module_composes_facts_from_cst(text, parsed);
194 let css_module_composes_edges = collect_css_module_composes_edge_facts_from_cst(text, parsed);
195 let icss = collect_icss_facts_from_cst(text, parsed);
196 let icss_import_edges = collect_icss_import_edge_facts_from_cst(text, parsed);
197 let icss_export_edges = collect_icss_export_edge_facts_from_cst(text, parsed);
198 let at_rules = collect_at_rule_facts_from_cst(text, parsed);
199
200 ParsedStyleFacts {
201 product: "omena-parser.style-facts",
202 dialect: parsed.dialect(),
203 selector_count: selectors.len(),
204 selectors,
205 variable_count: variables.len(),
206 variables,
207 sass_symbol_count: sass_symbols.len(),
208 sass_symbols,
209 sass_include_count: sass_includes.len(),
210 sass_includes,
211 sass_module_edge_count: sass_module_edges.len(),
212 sass_module_edges,
213 sass_placeholder_definition_count: sass_placeholder_definitions.len(),
214 sass_placeholder_definitions,
215 extend_target_count: extend_targets.len(),
216 extend_targets,
217 animation_count: animations.len(),
218 animations,
219 css_module_value_count: css_module_values.len(),
220 css_module_values,
221 css_module_value_import_edge_count: css_module_value_import_edges.len(),
222 css_module_value_import_edges,
223 css_module_value_definition_edge_count: css_module_value_definition_edges.len(),
224 css_module_value_definition_edges,
225 css_module_composes_count: css_module_composes.len(),
226 css_module_composes,
227 css_module_composes_edge_count: css_module_composes_edges.len(),
228 css_module_composes_edges,
229 icss_count: icss.len(),
230 icss,
231 icss_import_edge_count: icss_import_edges.len(),
232 icss_import_edges,
233 icss_export_edge_count: icss_export_edges.len(),
234 icss_export_edges,
235 at_rule_count: at_rules.len(),
236 at_rules,
237 error_count: parsed.errors().len(),
238 }
239}
240
241pub(crate) fn product_facts_from_cst(text: &str, parsed: &ParseResult) -> ParsedStyleFacts {
242 ProductFacts::from(facts_from_cst(text, parsed)).into()
243}
244
245#[cfg(test)]
246mod product_facts_authority_tests;
247
248pub(crate) fn tokens_from_syntax_node<'text>(
249 text: &'text str,
250 parsed: &ParseResult,
251 node: &SyntaxNode<SyntaxKind>,
252) -> Vec<Token<'text>> {
253 let node_range = node.text_range();
254 let tokens = parsed.syntax_token_views();
255 let start_index = tokens.partition_point(|token| token.range.start() < node_range.start());
256 let end_index = tokens[start_index..]
257 .partition_point(|token| token.range.start() < node_range.end())
258 + start_index;
259 tokens[start_index..end_index]
260 .iter()
261 .filter(|token| token.range.end() <= node_range.end())
262 .map(|token| {
263 let range = token.range;
264 let start = u32::from(range.start()) as usize;
265 let end = u32::from(range.end()) as usize;
266 Token {
267 kind: token.kind,
268 text: text.get(start..end).unwrap_or_default(),
269 range,
270 }
271 })
272 .collect()
273}
274
275pub(crate) fn syntax_node_is_top_level(node: &SyntaxNode<SyntaxKind>) -> bool {
276 node.parent().is_some_and(|parent| {
277 matches!(
278 parent.kind(),
279 SyntaxKind::Stylesheet | SyntaxKind::ScssStylesheet | SyntaxKind::LessStylesheet
280 )
281 })
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::{StyleDialect, parse};
288
289 fn tokens_from_syntax_node_linear<'text>(
290 text: &'text str,
291 parsed: &ParseResult,
292 node: &SyntaxNode<SyntaxKind>,
293 ) -> Vec<Token<'text>> {
294 let node_range = node.text_range();
295 parsed
296 .syntax_token_views()
297 .iter()
298 .filter(|token| token.range.start() >= node_range.start())
299 .filter(|token| token.range.end() <= node_range.end())
300 .map(|token| {
301 let range = token.range;
302 let start = u32::from(range.start()) as usize;
303 let end = u32::from(range.end()) as usize;
304 Token {
305 kind: token.kind,
306 text: text.get(start..end).unwrap_or_default(),
307 range,
308 }
309 })
310 .collect()
311 }
312
313 #[test]
314 fn tokens_from_syntax_node_matches_linear_scan_order() {
315 let text = r#"@use "./tokens" as t;
316:export { exported: local; }
317.button, :global(.card) {
318 --gap: 1rem;
319 color: var(--brand);
320 &__icon { composes: icon from "./icons.module.css"; }
321}
322@media (width >= 1px) {
323 .button--primary { color: t.$brand; }
324}"#;
325 let parsed = parse(text, StyleDialect::Scss);
326 let syntax = parsed.syntax();
327
328 for node in syntax.descendants() {
329 assert_eq!(
330 tokens_from_syntax_node(text, &parsed, node),
331 tokens_from_syntax_node_linear(text, &parsed, node),
332 "token slice drift for {:?} at {:?}",
333 node.kind(),
334 node.text_range()
335 );
336 }
337 }
338}