Skip to main content

omena_transform_passes/
registry.rs

1use omena_cascade::{
2    CascadeValue, CustomPropertyLeastFixedPointSummaryV0, StaticSupportsAssumptionV0,
3    summarize_custom_property_least_fixed_point,
4};
5use omena_parser::StyleDialect;
6use omena_scss_eval::{
7    summarize_native_css_static_edit_plan, summarize_native_css_static_edit_plan_from_transform_ir,
8};
9use omena_syntax::ident::AuthoredPropertyTextV0;
10use omena_transform_cst::TransformIrV0;
11
12use crate::domains::{
13    calc::reduce_css_calc_with_lexer,
14    cascade_flatten::{
15        flatten_css_layers_with_ir_transaction_on_ir, flatten_css_scopes_with_ir_transaction_on_ir,
16    },
17    color::compress_css_colors_with_lexer,
18    color_lowering::{
19        lower_css_color_function_with_lexer, lower_css_color_mix_with_lexer,
20        lower_css_light_dark_with_lexer, lower_css_oklab_oklch_with_lexer,
21        lower_relative_color_with_lexer,
22    },
23    css_modules_classes::{
24        local_css_module_composes_resolutions_from_ir,
25        rewrite_css_module_class_names_with_ir_transaction_on_ir,
26        strip_resolved_css_module_composes_with_ir_transaction_on_ir,
27        tree_shake_css_class_rules_with_ir_transaction_on_ir,
28    },
29    css_modules_values::{
30        resolve_static_css_modules_values_with_lexer,
31        tree_shake_css_modules_values_with_ir_transaction_on_ir,
32    },
33    custom_property::{
34        collect_static_root_custom_property_env, parse_static_custom_property_env_value,
35        substitute_static_css_custom_properties_with_lexer,
36        tree_shake_css_custom_properties_with_ir_transaction_on_ir,
37    },
38    design_token::route_design_token_values_with_ir_transaction_on_ir,
39    import_inline::{
40        inline_css_imports_for_static_module_evaluation_with_lexer,
41        inline_css_imports_with_ir_transaction, inline_css_imports_with_ir_transaction_on_ir,
42        restore_less_inline_literal_placeholders as restore_less_inline_literal_placeholders_with_lexer,
43    },
44    keyframes::tree_shake_css_keyframes_with_ir_transaction_on_ir,
45    logical::lower_css_logical_to_physical_with_lexer,
46    nesting::unwrap_css_nesting_with_ir_transaction_on_ir,
47    number::compress_css_numbers_with_lexer,
48    rule_cleanup::{
49        dedupe_exact_css_rules_with_ir_transaction_on_ir,
50        remove_empty_css_rules_with_ir_transaction_on_ir,
51    },
52    rule_merge::{
53        merge_adjacent_same_block_css_selectors_with_ir_transaction_on_ir,
54        merge_adjacent_same_selector_css_rules_with_ir_transaction_on_ir,
55    },
56    selector::compress_css_is_where_selectors_with_lexer,
57    shorthand::combine_css_shorthands_with_lexer,
58    static_eval::{
59        StaticMediaEvaluationOptions, evaluate_static_container_rules_with_ir_transaction_on_ir,
60        evaluate_static_media_rules_with_ir_transaction_on_ir,
61        evaluate_static_supports_rules_with_ir_transaction_on_ir,
62    },
63    text::{
64        normalize_css_font_declarations_with_lexer, normalize_css_string_quotes_with_lexer,
65        strip_css_url_quotes_with_lexer,
66    },
67    trivia::{normalize_css_whitespace_with_lexer, strip_css_comments_with_lexer},
68    unit::normalize_css_units_with_lexer,
69    vendor_prefix::{
70        add_css_vendor_prefixes_with_lexer_and_policy, remove_stale_css_vendor_prefixes_with_lexer,
71    },
72};
73use crate::helpers::ir_transaction::{
74    TransformIrReplacementKindV0, TransformIrSourceReplacementErrorV0,
75    TransformIrSourceReplacementV0, replace_ir_node_spans_in_ir,
76};
77use crate::helpers::rules::collect_top_level_ordinary_rule_slices;
78use crate::model::{
79    TransformClassNameRewriteV0, TransformCssModuleComposesResolutionV0,
80    TransformCssModuleValueResolutionV0, TransformDesignTokenRouteV0, TransformImportInlineV0,
81    TransformLessInlineLiteralPlaceholderV0, TransformSemanticRemovalCandidate,
82    TransformVendorPrefixPolicyV0,
83};
84use crate::runtime::lex_cache::lex_cached as lex;
85
86pub(crate) fn strip_css_comments(source: &str, dialect: StyleDialect) -> (String, usize) {
87    strip_css_comments_with_lexer(source, dialect)
88}
89
90pub(crate) fn compress_css_numbers(source: &str, dialect: StyleDialect) -> (String, usize) {
91    compress_css_numbers_with_lexer(source, dialect)
92}
93
94pub(crate) fn compress_css_colors(source: &str, dialect: StyleDialect) -> (String, usize) {
95    compress_css_colors_with_lexer(source, dialect)
96}
97
98pub(crate) fn normalize_css_units(source: &str, dialect: StyleDialect) -> (String, usize) {
99    normalize_css_units_with_lexer(source, dialect)
100}
101
102pub(crate) fn strip_css_url_quotes(source: &str, dialect: StyleDialect) -> (String, usize) {
103    strip_css_url_quotes_with_lexer(source, dialect)
104}
105
106pub(crate) fn normalize_css_string_quotes(source: &str, dialect: StyleDialect) -> (String, usize) {
107    let (source, font_declaration_mutations) =
108        normalize_css_font_declarations_with_lexer(source, dialect);
109    let (source, token_mutations) = normalize_css_string_quotes_with_lexer(&source, dialect);
110    (source, font_declaration_mutations + token_mutations)
111}
112
113pub(crate) fn compress_css_is_where_selectors(
114    source: &str,
115    dialect: StyleDialect,
116) -> (String, usize) {
117    compress_css_is_where_selectors_with_lexer(source, dialect)
118}
119
120pub(crate) fn remove_empty_css_rules_in_ir(
121    ir: &mut TransformIrV0,
122    dialect: StyleDialect,
123) -> Result<usize, TransformIrSourceReplacementErrorV0> {
124    remove_empty_css_rules_with_ir_transaction_on_ir(ir, dialect)
125}
126
127pub(crate) fn combine_css_shorthands(source: &str, dialect: StyleDialect) -> (String, usize) {
128    combine_css_shorthands_with_lexer(source, dialect)
129}
130
131pub(crate) fn dedupe_exact_css_rules_in_ir(
132    ir: &mut TransformIrV0,
133    dialect: StyleDialect,
134) -> Result<usize, TransformIrSourceReplacementErrorV0> {
135    dedupe_exact_css_rules_with_ir_transaction_on_ir(ir, dialect)
136}
137
138pub(crate) fn merge_adjacent_same_selector_css_rules_in_ir(
139    ir: &mut TransformIrV0,
140    dialect: StyleDialect,
141) -> Result<usize, TransformIrSourceReplacementErrorV0> {
142    merge_adjacent_same_selector_css_rules_with_ir_transaction_on_ir(ir, dialect)
143}
144
145pub(crate) fn merge_adjacent_same_block_css_selectors_in_ir(
146    ir: &mut TransformIrV0,
147    dialect: StyleDialect,
148) -> Result<usize, TransformIrSourceReplacementErrorV0> {
149    merge_adjacent_same_block_css_selectors_with_ir_transaction_on_ir(ir, dialect)
150}
151
152pub(crate) fn add_css_vendor_prefixes(
153    source: &str,
154    dialect: StyleDialect,
155    policy: TransformVendorPrefixPolicyV0,
156) -> (String, usize) {
157    add_css_vendor_prefixes_with_lexer_and_policy(source, dialect, policy)
158}
159
160pub(crate) fn remove_stale_css_vendor_prefixes(
161    source: &str,
162    dialect: StyleDialect,
163) -> (String, usize) {
164    remove_stale_css_vendor_prefixes_with_lexer(source, dialect)
165}
166
167pub(crate) fn lower_css_light_dark(source: &str, dialect: StyleDialect) -> (String, usize) {
168    lower_css_light_dark_with_lexer(source, dialect)
169}
170
171pub(crate) fn lower_css_color_mix(source: &str, dialect: StyleDialect) -> (String, usize) {
172    lower_css_color_mix_with_lexer(source, dialect)
173}
174
175pub(crate) fn lower_css_oklab_oklch(source: &str, dialect: StyleDialect) -> (String, usize) {
176    lower_css_oklab_oklch_with_lexer(source, dialect)
177}
178
179pub(crate) fn lower_css_color_function(source: &str, dialect: StyleDialect) -> (String, usize) {
180    lower_css_color_function_with_lexer(source, dialect)
181}
182
183pub(crate) fn lower_relative_color(source: &str, dialect: StyleDialect) -> (String, usize) {
184    lower_relative_color_with_lexer(source, dialect)
185}
186
187pub(crate) fn lower_css_logical_to_physical(
188    source: &str,
189    dialect: StyleDialect,
190) -> (String, usize) {
191    lower_css_logical_to_physical_with_lexer(source, dialect)
192}
193
194pub(crate) fn unwrap_css_nesting_in_ir(
195    ir: &mut TransformIrV0,
196    dialect: StyleDialect,
197) -> Result<usize, TransformIrSourceReplacementErrorV0> {
198    unwrap_css_nesting_with_ir_transaction_on_ir(ir, dialect)
199}
200
201pub(crate) fn flatten_css_scopes_in_ir(
202    ir: &mut TransformIrV0,
203    dialect: StyleDialect,
204) -> Result<usize, TransformIrSourceReplacementErrorV0> {
205    flatten_css_scopes_with_ir_transaction_on_ir(ir, dialect)
206}
207
208pub(crate) fn flatten_css_layers_in_ir(
209    ir: &mut TransformIrV0,
210    dialect: StyleDialect,
211    closed_bundle: bool,
212) -> Result<usize, TransformIrSourceReplacementErrorV0> {
213    flatten_css_layers_with_ir_transaction_on_ir(ir, dialect, closed_bundle)
214}
215
216pub(crate) fn evaluate_static_supports_rules_in_ir(
217    ir: &mut TransformIrV0,
218    dialect: StyleDialect,
219    assumption: StaticSupportsAssumptionV0,
220) -> Result<usize, TransformIrSourceReplacementErrorV0> {
221    evaluate_static_supports_rules_with_ir_transaction_on_ir(ir, dialect, assumption)
222}
223
224pub(crate) fn evaluate_static_media_rules_in_ir(
225    ir: &mut TransformIrV0,
226    dialect: StyleDialect,
227) -> Result<usize, TransformIrSourceReplacementErrorV0> {
228    evaluate_static_media_rules_with_ir_transaction_on_ir(
229        ir,
230        dialect,
231        StaticMediaEvaluationOptions::default(),
232    )
233}
234
235pub(crate) fn evaluate_static_container_rules_in_ir(
236    ir: &mut TransformIrV0,
237    dialect: StyleDialect,
238) -> Result<usize, TransformIrSourceReplacementErrorV0> {
239    evaluate_static_container_rules_with_ir_transaction_on_ir(ir, dialect)
240}
241
242pub(crate) fn evaluate_native_css_static_values_with_plan(
243    source: &str,
244    dialect: StyleDialect,
245) -> (String, usize) {
246    if dialect != StyleDialect::Css {
247        return (source.to_string(), 0);
248    }
249    let Some(plan) = summarize_native_css_static_edit_plan(source, dialect) else {
250        return (source.to_string(), 0);
251    };
252    let mutation_count = if plan.output_changed {
253        plan.edit_count
254    } else {
255        0
256    };
257    (plan.edited_css, mutation_count)
258}
259
260pub(crate) fn evaluate_native_css_static_values_in_ir(
261    ir: &mut TransformIrV0,
262    dialect: StyleDialect,
263) -> Result<usize, TransformIrSourceReplacementErrorV0> {
264    if dialect != StyleDialect::Css {
265        return Ok(0);
266    }
267    let Some(plan) = summarize_native_css_static_edit_plan_from_transform_ir(ir, dialect) else {
268        return Ok(0);
269    };
270    let replacements = plan
271        .edits
272        .into_iter()
273        .map(|edit| TransformIrSourceReplacementV0 {
274            source_span_start: edit.start,
275            source_span_end: edit.end,
276            replacement: edit.replacement,
277            kind: match edit.edit_kind {
278                "whenRuleBranchFold" | "ifRuleBranchFold" => TransformIrReplacementKindV0::AtRule,
279                _ => TransformIrReplacementKindV0::Declaration,
280            },
281        })
282        .collect::<Vec<_>>();
283    replace_ir_node_spans_in_ir(ir, "native-css-static-eval", replacements.as_slice())
284}
285
286pub(crate) fn evaluate_dead_media_branch_rules_in_ir(
287    ir: &mut TransformIrV0,
288    dialect: StyleDialect,
289    drop_dark_mode_media_queries: bool,
290) -> Result<usize, TransformIrSourceReplacementErrorV0> {
291    evaluate_static_media_rules_with_ir_transaction_on_ir(
292        ir,
293        dialect,
294        StaticMediaEvaluationOptions {
295            drop_dark_mode_media_queries,
296        },
297    )
298}
299
300/// Applies resolved CSS `@import` replacements for the import-inline pass.
301pub fn inline_css_imports(
302    source: &str,
303    dialect: StyleDialect,
304    inlines: &[TransformImportInlineV0],
305) -> (String, usize) {
306    inline_css_imports_with_ir_transaction(source, dialect, inlines)
307        .unwrap_or_else(|_| (source.to_string(), 0))
308}
309
310pub(crate) fn inline_css_imports_in_ir(
311    ir: &mut TransformIrV0,
312    dialect: StyleDialect,
313    inlines: &[TransformImportInlineV0],
314) -> Result<usize, TransformIrSourceReplacementErrorV0> {
315    inline_css_imports_with_ir_transaction_on_ir(ir, dialect, inlines)
316}
317
318/// Applies import inlining before static Sass/Less module evaluation.
319pub fn inline_css_imports_for_static_module_evaluation(
320    source: &str,
321    dialect: StyleDialect,
322    inlines: &[TransformImportInlineV0],
323) -> (String, usize, Vec<TransformLessInlineLiteralPlaceholderV0>) {
324    inline_css_imports_for_static_module_evaluation_with_lexer(source, dialect, inlines)
325}
326
327/// Restores Less `(inline)` literal import placeholders after static evaluation.
328pub fn restore_less_inline_literal_placeholders(
329    source: &str,
330    placeholders: &[TransformLessInlineLiteralPlaceholderV0],
331) -> String {
332    restore_less_inline_literal_placeholders_with_lexer(source, placeholders)
333}
334
335pub(crate) fn resolve_static_css_modules_values(
336    source: &str,
337    dialect: StyleDialect,
338    resolutions: &[TransformCssModuleValueResolutionV0],
339) -> (String, usize) {
340    resolve_static_css_modules_values_with_lexer(source, dialect, resolutions)
341}
342
343pub(crate) fn resolve_css_module_composes_in_ir(
344    ir: &mut TransformIrV0,
345    dialect: StyleDialect,
346    resolutions: &[TransformCssModuleComposesResolutionV0],
347) -> Result<usize, TransformIrSourceReplacementErrorV0> {
348    strip_resolved_css_module_composes_with_ir_transaction_on_ir(ir, dialect, resolutions)
349}
350
351pub(crate) fn css_module_composes_resolutions_for_ir(
352    ir: &TransformIrV0,
353    _dialect: StyleDialect,
354    resolutions: &[TransformCssModuleComposesResolutionV0],
355) -> Vec<TransformCssModuleComposesResolutionV0> {
356    let mut merged = local_css_module_composes_resolutions_from_ir(ir);
357    for resolution in resolutions {
358        let Some(existing) = merged
359            .iter_mut()
360            .find(|existing| existing.local_class_name == resolution.local_class_name)
361        else {
362            merged.push(resolution.clone());
363            continue;
364        };
365        for exported_class_name in &resolution.exported_class_names {
366            if !existing
367                .exported_class_names
368                .iter()
369                .any(|existing| existing == exported_class_name)
370            {
371                existing
372                    .exported_class_names
373                    .push(exported_class_name.clone());
374            }
375        }
376    }
377    merged.sort_by(|left, right| left.local_class_name.cmp(&right.local_class_name));
378    merged
379}
380
381pub(crate) fn route_design_token_values_in_ir(
382    ir: &mut TransformIrV0,
383    dialect: StyleDialect,
384    routes: &[TransformDesignTokenRouteV0],
385) -> Result<usize, TransformIrSourceReplacementErrorV0> {
386    route_design_token_values_with_ir_transaction_on_ir(ir, dialect, routes)
387}
388
389pub(crate) fn tree_shake_css_class_rules_in_ir(
390    ir: &mut TransformIrV0,
391    dialect: StyleDialect,
392    reachable_class_names: &[String],
393) -> Result<Vec<TransformSemanticRemovalCandidate>, TransformIrSourceReplacementErrorV0> {
394    tree_shake_css_class_rules_with_ir_transaction_on_ir(ir, dialect, reachable_class_names)
395}
396
397pub(crate) fn tree_shake_css_keyframes_in_ir(
398    ir: &mut TransformIrV0,
399    dialect: StyleDialect,
400    reachable_keyframe_names: &[String],
401    reachable_class_names: &[String],
402) -> Result<Vec<TransformSemanticRemovalCandidate>, TransformIrSourceReplacementErrorV0> {
403    tree_shake_css_keyframes_with_ir_transaction_on_ir(
404        ir,
405        dialect,
406        reachable_keyframe_names,
407        reachable_class_names,
408    )
409}
410
411pub(crate) fn tree_shake_css_modules_values_in_ir(
412    ir: &mut TransformIrV0,
413    dialect: StyleDialect,
414    reachable_value_names: &[String],
415    reachable_keyframe_names: &[String],
416    reachable_class_names: &[String],
417) -> Result<Vec<TransformSemanticRemovalCandidate>, TransformIrSourceReplacementErrorV0> {
418    tree_shake_css_modules_values_with_ir_transaction_on_ir(
419        ir,
420        dialect,
421        reachable_value_names,
422        reachable_keyframe_names,
423        reachable_class_names,
424    )
425}
426
427pub(crate) fn tree_shake_css_custom_properties_in_ir(
428    ir: &mut TransformIrV0,
429    dialect: StyleDialect,
430    reachable_custom_property_names: &[AuthoredPropertyTextV0],
431    reachable_keyframe_names: &[String],
432    reachable_class_names: &[String],
433) -> Result<Vec<TransformSemanticRemovalCandidate>, TransformIrSourceReplacementErrorV0> {
434    tree_shake_css_custom_properties_with_ir_transaction_on_ir(
435        ir,
436        dialect,
437        reachable_custom_property_names,
438        reachable_keyframe_names,
439        reachable_class_names,
440    )
441}
442
443pub(crate) fn rewrite_css_module_class_names_in_ir(
444    ir: &mut TransformIrV0,
445    dialect: StyleDialect,
446    rewrites: &[TransformClassNameRewriteV0],
447) -> Result<usize, TransformIrSourceReplacementErrorV0> {
448    rewrite_css_module_class_names_with_ir_transaction_on_ir(ir, dialect, rewrites)
449}
450
451pub(crate) fn substitute_static_css_custom_properties(
452    source: &str,
453    dialect: StyleDialect,
454) -> (String, usize) {
455    substitute_static_css_custom_properties_with_lexer(source, dialect)
456}
457
458/// Summarizes the static custom-property least fixed point for a style source.
459pub fn summarize_static_css_custom_property_fixed_point_from_source(
460    source: &str,
461    dialect: StyleDialect,
462) -> CustomPropertyLeastFixedPointSummaryV0 {
463    let lexed = lex(source, dialect);
464    let tokens = lexed.tokens();
465    let env_rules = collect_top_level_ordinary_rule_slices(source, tokens);
466    let env = collect_static_root_custom_property_env(tokens, &env_rules);
467    summarize_custom_property_least_fixed_point(&env)
468}
469
470/// Parses a static CSS value into the cascade value model used by query consumers.
471pub fn parse_static_css_cascade_value(value: &str) -> Option<CascadeValue> {
472    match value.trim() {
473        "initial" => Some(CascadeValue::Initial),
474        "inherit" => Some(CascadeValue::Inherit),
475        "unset" => Some(CascadeValue::Unset),
476        value => parse_static_custom_property_env_value(value),
477    }
478}
479
480pub(crate) fn reduce_css_calc(source: &str, dialect: StyleDialect) -> (String, usize) {
481    reduce_css_calc_with_lexer(source, dialect)
482}
483
484pub(crate) fn normalize_css_whitespace(source: &str, dialect: StyleDialect) -> (String, usize) {
485    normalize_css_whitespace_with_lexer(source, dialect)
486}