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