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