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 });
122
123 OmenaQueryRegisteredCustomPropertyComputedValueV0 {
124 schema_version: "0",
125 product: "omena-query.registered-custom-property-computed-value",
126 style_uri: style_uri.to_string(),
127 selector: selector.to_string(),
128 property: property.to_string(),
129 registration_applied,
130 registration_projection_complete,
131 matched_value_count,
132 unmatched_value_count,
133 unknown_value_count,
134 computed_value,
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use omena_cascade::{ComputedCascadeIndeterminateReasonV0, ComputedCascadeValueStatusV0};
142
143 #[test]
144 fn registered_properties_use_typed_syntax_inheritance_and_initial_values() {
145 let source = r#"
146@property --gap {
147 syntax: '<length>';
148 inherits: false;
149 initial-value: 8px;
150}
151.valid { --gap: 12px; }
152.invalid { --gap: red; }
153"#;
154
155 let valid = summarize_omena_query_registered_custom_property_computed_value_v0(
156 "tokens.css",
157 source,
158 ".valid",
159 "--gap",
160 Some(CascadeValue::Literal("16px".to_string())),
161 );
162 assert!(valid.registration_applied);
163 assert!(valid.registration_projection_complete);
164 assert_eq!(valid.matched_value_count, 1);
165 assert_eq!(valid.unmatched_value_count, 0);
166 assert_eq!(
167 valid.computed_value.status,
168 ComputedCascadeValueStatusV0::Resolved
169 );
170 assert_eq!(
171 valid.computed_value.value,
172 CascadeValue::Literal("12px".to_string())
173 );
174
175 let invalid = summarize_omena_query_registered_custom_property_computed_value_v0(
176 "tokens.css",
177 source,
178 ".invalid",
179 "--gap",
180 Some(CascadeValue::Literal("16px".to_string())),
181 );
182 assert_eq!(invalid.matched_value_count, 0);
183 assert_eq!(invalid.unmatched_value_count, 1);
184 assert_eq!(
185 invalid.computed_value.status,
186 ComputedCascadeValueStatusV0::InvalidAtComputedValueTime
187 );
188 assert_eq!(
189 invalid.computed_value.value,
190 CascadeValue::Literal("8px".to_string())
191 );
192 assert!(invalid.computed_value.invalid_at_computed_value_time);
193
194 let absent = summarize_omena_query_registered_custom_property_computed_value_v0(
195 "tokens.css",
196 source,
197 ".absent",
198 "--gap",
199 Some(CascadeValue::Literal("16px".to_string())),
200 );
201 assert_eq!(
202 absent.computed_value.status,
203 ComputedCascadeValueStatusV0::Initial
204 );
205 assert_eq!(
206 absent.computed_value.value,
207 CascadeValue::Literal("8px".to_string())
208 );
209 }
210
211 #[test]
212 fn indeterminate_registered_syntax_has_a_typed_reason() {
213 let source = r#"
214@property --gap {
215 syntax: '<length>';
216 inherits: false;
217 initial-value: 8px;
218}
219.deferred { --gap: var(--runtime-value); }
220"#;
221
222 let result = summarize_omena_query_registered_custom_property_computed_value_v0(
223 "tokens.css",
224 source,
225 ".deferred",
226 "--gap",
227 None,
228 );
229
230 assert_eq!(result.unknown_value_count, 1);
231 assert_eq!(
232 result.computed_value.status,
233 ComputedCascadeValueStatusV0::Indeterminate
234 );
235 assert_eq!(result.computed_value.value, CascadeValue::Indeterminate);
236 assert!(!result.computed_value.invalid_at_computed_value_time);
237 assert_eq!(
238 result.computed_value.indeterminate_reason,
239 Some(ComputedCascadeIndeterminateReasonV0::RegisteredPropertySyntaxIndeterminate)
240 );
241 }
242
243 #[test]
244 fn unregistered_custom_properties_preserve_the_existing_computed_path() {
245 let report = summarize_omena_query_registered_custom_property_computed_value_v0(
246 "tokens.css",
247 ".target { --legacy-gap: 12px; }",
248 ".target",
249 "--legacy-gap",
250 Some(CascadeValue::Literal("16px".to_string())),
251 );
252
253 assert!(!report.registration_applied);
254 assert!(report.registration_projection_complete);
255 assert_eq!(report.matched_value_count, 0);
256 assert_eq!(report.unmatched_value_count, 0);
257 assert_eq!(report.unknown_value_count, 0);
258 assert_eq!(
259 report.computed_value.status,
260 ComputedCascadeValueStatusV0::Resolved
261 );
262 assert_eq!(
263 report.computed_value.value,
264 CascadeValue::Literal("12px".to_string())
265 );
266 assert_eq!(
267 report.computed_value.derivation_steps,
268 vec![
269 "cascadeWinnerSelected",
270 "computedValueResolutionStarted",
271 "computedValueResolved",
272 ]
273 );
274 }
275}