1use std::collections::BTreeMap;
4
5use omena_cascade::{
6 CascadeComputedValueInputV0, CascadeComputedValueResultV0, CascadeRegisteredCustomPropertyV0,
7 CascadeRegisteredValueVerdictV0, CascadeValue, CustomPropertyEnv,
8 compute_cascade_computed_value,
9};
10use omena_query_checker_orchestrator::active_omena_checker_custom_property_registrations_v0;
11use omena_query_core::{CssValueValidationClassV0, validate_registered_property_value_v0};
12use omena_query_transform_runner::parse_static_css_cascade_value;
13use serde::Serialize;
14
15use super::cascade_checker::{
16 collect_query_checker_cascade_declarations,
17 collect_query_checker_custom_property_registrations,
18 query_runtime_cascade_declaration_from_input,
19};
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct OmenaQueryRegisteredCustomPropertyComputedValueV0 {
24 pub schema_version: &'static str,
25 pub product: &'static str,
26 pub style_uri: String,
27 pub selector: String,
28 pub property: String,
29 pub registration_applied: bool,
30 pub registration_projection_complete: bool,
31 pub matched_value_count: usize,
32 pub unmatched_value_count: usize,
33 pub unknown_value_count: usize,
34 pub computed_value: CascadeComputedValueResultV0,
35}
36
37pub fn summarize_omena_query_registered_custom_property_computed_value_v0(
38 style_uri: &str,
39 source: &str,
40 selector: &str,
41 property: &str,
42 parent_computed_value: Option<CascadeValue>,
43) -> OmenaQueryRegisteredCustomPropertyComputedValueV0 {
44 let registration_inputs =
45 collect_query_checker_custom_property_registrations(style_uri, source);
46 let active_registrations =
47 active_omena_checker_custom_property_registrations_v0(registration_inputs.as_slice());
48 let active_registration = active_registrations.get(property);
49 let mut verdicts = BTreeMap::new();
50 let mut matched_value_count = 0usize;
51 let mut unmatched_value_count = 0usize;
52 let mut unknown_value_count = 0usize;
53 let declarations = collect_query_checker_cascade_declarations(source)
54 .into_iter()
55 .filter(|declaration| {
56 declaration.input.property == property
57 && declaration.input.selector.as_str() == selector
58 && declaration.input.condition_context.is_empty()
59 })
60 .map(|declaration| {
61 let mut cascade_declaration =
62 query_runtime_cascade_declaration_from_input(&declaration.input);
63 cascade_declaration.value =
64 parse_static_css_cascade_value(declaration.input.value.as_str())
65 .unwrap_or(CascadeValue::GuaranteedInvalid);
66 if let Some(registration) = active_registration {
67 let verdict = match validate_registered_property_value_v0(
68 registration.syntax.as_str(),
69 declaration.input.value.as_str(),
70 )
71 .class
72 {
73 CssValueValidationClassV0::Valid => {
74 matched_value_count += 1;
75 CascadeRegisteredValueVerdictV0::Matched
76 }
77 CssValueValidationClassV0::Invalid => {
78 unmatched_value_count += 1;
79 CascadeRegisteredValueVerdictV0::Unmatched
80 }
81 CssValueValidationClassV0::NotValidatable => {
82 unknown_value_count += 1;
83 CascadeRegisteredValueVerdictV0::Unknown
84 }
85 };
86 verdicts.insert(cascade_declaration.id.clone(), verdict);
87 }
88 cascade_declaration
89 })
90 .collect::<Vec<_>>();
91
92 let (registered_custom_property, registration_projection_complete) = match active_registration {
93 Some(registration) => {
94 let initial_value = registration
95 .initial_value
96 .as_deref()
97 .map(parse_static_css_cascade_value)
98 .unwrap_or(Some(CascadeValue::GuaranteedInvalid));
99 match initial_value {
100 Some(initial_value) => (
101 Some(CascadeRegisteredCustomPropertyV0 {
102 name: registration.name.clone(),
103 inherits: registration.inherits,
104 initial_value,
105 declaration_value_verdicts: verdicts,
106 }),
107 true,
108 ),
109 None => (None, false),
110 }
111 }
112 None => (None, true),
113 };
114 let registration_applied = registered_custom_property.is_some();
115 let computed_value = compute_cascade_computed_value(CascadeComputedValueInputV0 {
116 property: property.to_string(),
117 declarations,
118 custom_property_env: CustomPropertyEnv::new(),
119 parent_computed_value,
120 registered_custom_property,
121 standard_property_value_verdicts: BTreeMap::new(),
122 });
123
124 OmenaQueryRegisteredCustomPropertyComputedValueV0 {
125 schema_version: "0",
126 product: "omena-query.registered-custom-property-computed-value",
127 style_uri: style_uri.to_string(),
128 selector: selector.to_string(),
129 property: property.to_string(),
130 registration_applied,
131 registration_projection_complete,
132 matched_value_count,
133 unmatched_value_count,
134 unknown_value_count,
135 computed_value,
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use omena_cascade::{ComputedCascadeIndeterminateReasonV0, ComputedCascadeValueStatusV0};
143
144 #[test]
145 fn registered_properties_use_typed_syntax_inheritance_and_initial_values() {
146 let source = r#"
147@property --gap {
148 syntax: '<length>';
149 inherits: false;
150 initial-value: 8px;
151}
152.valid { --gap: 12px; }
153.invalid { --gap: red; }
154"#;
155
156 let valid = summarize_omena_query_registered_custom_property_computed_value_v0(
157 "tokens.css",
158 source,
159 ".valid",
160 "--gap",
161 Some(CascadeValue::Literal("16px".to_string())),
162 );
163 assert!(valid.registration_applied);
164 assert!(valid.registration_projection_complete);
165 assert_eq!(valid.matched_value_count, 1);
166 assert_eq!(valid.unmatched_value_count, 0);
167 assert_eq!(
168 valid.computed_value.status,
169 ComputedCascadeValueStatusV0::Resolved
170 );
171 assert_eq!(
172 valid.computed_value.value,
173 CascadeValue::Literal("12px".to_string())
174 );
175
176 let invalid = summarize_omena_query_registered_custom_property_computed_value_v0(
177 "tokens.css",
178 source,
179 ".invalid",
180 "--gap",
181 Some(CascadeValue::Literal("16px".to_string())),
182 );
183 assert_eq!(invalid.matched_value_count, 0);
184 assert_eq!(invalid.unmatched_value_count, 1);
185 assert_eq!(
186 invalid.computed_value.status,
187 ComputedCascadeValueStatusV0::InvalidAtComputedValueTime
188 );
189 assert_eq!(
190 invalid.computed_value.value,
191 CascadeValue::Literal("8px".to_string())
192 );
193 assert!(invalid.computed_value.invalid_at_computed_value_time);
194
195 let absent = summarize_omena_query_registered_custom_property_computed_value_v0(
196 "tokens.css",
197 source,
198 ".absent",
199 "--gap",
200 Some(CascadeValue::Literal("16px".to_string())),
201 );
202 assert_eq!(
203 absent.computed_value.status,
204 ComputedCascadeValueStatusV0::Initial
205 );
206 assert_eq!(
207 absent.computed_value.value,
208 CascadeValue::Literal("8px".to_string())
209 );
210 }
211
212 #[test]
213 fn indeterminate_registered_syntax_has_a_typed_reason() {
214 let source = r#"
215@property --gap {
216 syntax: '<length>';
217 inherits: false;
218 initial-value: 8px;
219}
220.deferred { --gap: var(--runtime-value); }
221"#;
222
223 let result = summarize_omena_query_registered_custom_property_computed_value_v0(
224 "tokens.css",
225 source,
226 ".deferred",
227 "--gap",
228 None,
229 );
230
231 assert_eq!(result.unknown_value_count, 1);
232 assert_eq!(
233 result.computed_value.status,
234 ComputedCascadeValueStatusV0::Indeterminate
235 );
236 assert_eq!(result.computed_value.value, CascadeValue::Indeterminate);
237 assert!(!result.computed_value.invalid_at_computed_value_time);
238 assert_eq!(
239 result.computed_value.indeterminate_reason,
240 Some(ComputedCascadeIndeterminateReasonV0::RegisteredPropertySyntaxIndeterminate)
241 );
242 }
243
244 #[test]
245 fn unregistered_custom_properties_preserve_the_existing_computed_path() {
246 let report = summarize_omena_query_registered_custom_property_computed_value_v0(
247 "tokens.css",
248 ".target { --legacy-gap: 12px; }",
249 ".target",
250 "--legacy-gap",
251 Some(CascadeValue::Literal("16px".to_string())),
252 );
253
254 assert!(!report.registration_applied);
255 assert!(report.registration_projection_complete);
256 assert_eq!(report.matched_value_count, 0);
257 assert_eq!(report.unmatched_value_count, 0);
258 assert_eq!(report.unknown_value_count, 0);
259 assert_eq!(
260 report.computed_value.status,
261 ComputedCascadeValueStatusV0::Resolved
262 );
263 assert_eq!(
264 report.computed_value.value,
265 CascadeValue::Literal("12px".to_string())
266 );
267 assert_eq!(
268 report.computed_value.derivation_steps,
269 vec![
270 "cascadeWinnerSelected",
271 "computedValueResolutionStarted",
272 "computedValueResolved",
273 ]
274 );
275 }
276}