omena_query/style/diagnostics/
single_file.rs1use super::shared::*;
2
3pub fn summarize_omena_query_missing_custom_property_diagnostics(
4 style_uri: &str,
5 source: &str,
6 candidates: &[OmenaQueryStyleHoverCandidateV0],
7) -> Vec<OmenaQueryStyleDiagnosticV0> {
8 let declaration_names = candidates
9 .iter()
10 .filter(|candidate| candidate.kind == "customPropertyDeclaration")
11 .map(|candidate| candidate.name.as_str())
12 .collect::<BTreeSet<_>>();
13 if declaration_names.is_empty() {
14 return Vec::new();
15 }
16
17 let dialect = omena_parser_dialect_for_style_path(style_uri);
24 let facts = collect_omena_query_omena_parser_style_facts_raw(source, dialect);
25 let fallback_ranges = facts
26 .variables
27 .iter()
28 .filter(|fact| {
29 fact.kind == ParsedVariableFactKind::CustomPropertyReference && fact.has_fallback
30 })
31 .map(|fact| {
32 let byte_span = ParserByteSpanV0 {
33 start: u32::from(fact.range.start()) as usize,
34 end: u32::from(fact.range.end()) as usize,
35 };
36 (
37 fact.name.clone(),
38 parser_range_for_byte_span(source, byte_span),
39 )
40 })
41 .collect::<BTreeSet<_>>();
42
43 let insertion_range = end_of_source_range(source);
44 candidates
45 .iter()
46 .filter(|candidate| {
47 candidate.kind == "customPropertyReference"
48 && !declaration_names.contains(candidate.name.as_str())
49 && !fallback_ranges.contains(&(candidate.name.clone(), candidate.range))
50 })
51 .map(|candidate| OmenaQueryStyleDiagnosticV0 {
52 code: "missingCustomProperty",
53 severity: "warning",
54 provenance: omena_query_evidence_graph_provenance![
55 "omena-parser.custom-property-facts",
56 "omena-query.style-diagnostics",
57 ],
58 range: candidate.range,
59 message: format!(
60 "CSS custom property '{}' not found in indexed style tokens.",
61 candidate.name
62 ),
63 tags: Vec::new(),
64 create_custom_property: Some(OmenaQueryCreateCustomPropertyActionV0 {
65 uri: style_uri.to_string(),
66 range: insertion_range,
67 new_text: format!("\n\n:root {{\n {}: ;\n}}\n", candidate.name),
68 property_name: candidate.name.clone(),
69 }),
70 cascade_narrowing: None,
71 cascade_confidence: None,
72 polynomial_provenance: None,
73 cross_file_scc: None,
74 })
75 .collect()
76}
77
78pub fn summarize_omena_query_cascade_aware_style_diagnostics(
79 style_uri: &str,
80 source: &str,
81 candidates: &[OmenaQueryStyleHoverCandidateV0],
82) -> Vec<OmenaQueryStyleDiagnosticV0> {
83 summarize_omena_query_cascade_aware_style_diagnostics_with_deep_analysis(
84 style_uri, source, candidates, false,
85 )
86}
87
88pub fn summarize_omena_query_cascade_aware_style_diagnostics_with_deep_analysis(
93 style_uri: &str,
94 source: &str,
95 candidates: &[OmenaQueryStyleHoverCandidateV0],
96 deep_analysis: bool,
97) -> Vec<OmenaQueryStyleDiagnosticV0> {
98 let declarations_by_name = candidates
99 .iter()
100 .filter(|candidate| candidate.kind == "customPropertyDeclaration")
101 .map(|candidate| (candidate.name.as_str(), candidate.range))
102 .collect::<BTreeMap<_, _>>();
103
104 let dialect = omena_parser_dialect_for_style_path(style_uri);
105 let mut diagnostics =
106 summarize_static_css_custom_property_fixed_point_from_source(source, dialect)
107 .entries
108 .into_iter()
109 .filter(|entry| entry.guaranteed_invalid)
110 .filter_map(|entry| {
111 declarations_by_name
112 .get(entry.name.as_str())
113 .copied()
114 .map(|range| OmenaQueryStyleDiagnosticV0 {
115 code: "guaranteedInvalidCustomProperty",
116 severity: "warning",
117 provenance: omena_query_evidence_graph_provenance![
118 "omena-transform-passes.custom-property-lfp",
119 "omena-query.cascade-aware-diagnostics",
120 ],
121 range,
122 message: format!(
123 "CSS custom property '{}' resolves to the guaranteed-invalid value.",
124 entry.name
125 ),
126 tags: Vec::new(),
127 create_custom_property: None,
128 cascade_narrowing: None,
129 cascade_confidence: None,
130 polynomial_provenance: None,
131 cross_file_scc: None,
132 })
133 })
134 .collect::<Vec<_>>();
135
136 diagnostics.extend(
137 summarize_query_cascade_checker_diagnostics_with_deep_analysis(
138 style_uri,
139 source,
140 deep_analysis,
141 ),
142 );
143
144 diagnostics
145}
146
147pub fn summarize_omena_query_missing_keyframes_diagnostics(
148 style_uri: &str,
149 source: &str,
150) -> Vec<OmenaQueryStyleDiagnosticV0> {
151 let dialect = omena_parser_dialect_for_style_path(style_uri);
152 let facts = collect_omena_query_omena_parser_style_facts_raw(source, dialect);
153 let declared_keyframes = facts
154 .animations
155 .iter()
156 .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
157 .map(|animation| animation.name.clone())
158 .collect::<BTreeSet<_>>();
159 let mut emitted = BTreeSet::new();
160
161 facts
162 .animations
163 .into_iter()
164 .filter(|animation| animation.kind == ParsedAnimationFactKind::AnimationNameReference)
165 .filter(|animation| !declared_keyframes.contains(animation.name.as_str()))
166 .filter_map(|animation| {
167 let start: u32 = animation.range.start().into();
168 let end: u32 = animation.range.end().into();
169 let byte_span = ParserByteSpanV0 {
170 start: start as usize,
171 end: end as usize,
172 };
173 if !emitted.insert((animation.name.clone(), byte_span.start, byte_span.end)) {
174 return None;
175 }
176 Some((animation, parser_range_for_byte_span(source, byte_span)))
177 })
178 .map(|(animation, range)| OmenaQueryStyleDiagnosticV0 {
179 code: "missingKeyframes",
180 severity: "warning",
181 provenance: omena_query_evidence_graph_provenance![
182 "omena-parser.animation-facts",
183 "omena-query.style-diagnostics",
184 ],
185 range,
186 message: format!("@keyframes '{}' not found in this file.", animation.name),
187 tags: Vec::new(),
188 create_custom_property: None,
189 cascade_narrowing: None,
190 cascade_confidence: None,
191 polynomial_provenance: None,
192 cross_file_scc: None,
193 })
194 .collect()
195}