bbnf_analysis/state/
pretty.rs1use std::collections::{HashMap, HashSet};
6
7use bbnf::generate::prettify::hints::{self, closest_hint, is_valid_hint};
8use ls_types::*;
9
10use crate::analysis::LineIndex;
11
12use super::types::{SemanticTokenInfo, token_types};
13
14#[derive(Debug, Clone)]
16pub struct PrettyInfo {
17 pub rule_name: String,
19 pub hints: Vec<String>,
21 pub span: (usize, usize),
23 pub rule_name_span: (usize, usize),
25 pub hint_spans: Vec<(usize, usize)>,
27}
28
29pub fn extract_pretties(
34 pretties: &[bbnf::types::PrettyDirective<'_>],
35 _src: &str,
36) -> Vec<PrettyInfo> {
37 pretties
38 .iter()
39 .map(|p| {
40 let dir_src = p.span.as_str();
41 let dir_start = p.span.start;
42
43 let rule_name = p.rule_name.as_ref();
45 let rule_name_offset = dir_src
46 .find(rule_name)
47 .map(|off| off + dir_start)
48 .unwrap_or_else(|| {
49 panic!(
50 "could not resolve @pretty rule-name span for `{}` within directive `{}`",
51 rule_name, dir_src
52 )
53 });
54 let rule_name_span = (rule_name_offset, rule_name_offset + rule_name.len());
55
56 let mut search_start = rule_name_offset + rule_name.len() - dir_start;
58 let hint_spans: Vec<(usize, usize)> = p
59 .hints
60 .iter()
61 .map(|hint| {
62 let hint_str = hint.as_ref();
63 let offset = dir_src[search_start..]
64 .find(hint_str)
65 .map(|off| off + search_start + dir_start)
66 .unwrap_or_else(|| {
67 panic!(
68 "could not resolve @pretty hint span for `{}` within directive `{}`",
69 hint_str, dir_src
70 )
71 });
72 search_start = offset - dir_start + hint_str.len();
73 (offset, offset + hint_str.len())
74 })
75 .collect();
76
77 PrettyInfo {
78 rule_name: rule_name.to_string(),
79 hints: p.hints.iter().map(|h| h.to_string()).collect(),
80 span: (p.span.start, p.span.end),
81 rule_name_span,
82 hint_spans,
83 }
84 })
85 .collect()
86}
87
88pub fn validate_pretties(
92 pretties: &[PrettyInfo],
93 defined: &HashMap<&str, usize>,
94 imported_names: &HashSet<&str>,
95 line_index: &LineIndex,
96) -> (Vec<Diagnostic>, Vec<SemanticTokenInfo>) {
97 let mut diagnostics = Vec::new();
98 let mut semantic_tokens = Vec::new();
99
100 for pretty in pretties {
101 semantic_tokens.push(SemanticTokenInfo {
104 span: (pretty.span.0, pretty.span.0 + 7),
105 token_type: token_types::KEYWORD,
106 });
107
108 semantic_tokens.push(SemanticTokenInfo {
110 span: pretty.rule_name_span,
111 token_type: token_types::RULE_REFERENCE,
112 });
113
114 if pretty.rule_name == "*" {
116 for (i, hint) in pretty.hints.iter().enumerate() {
118 let valid_modes = ["auto", "minimal", "off"];
119 if !valid_modes.contains(&hint.as_str()) {
120 let span = pretty
121 .hint_spans
122 .get(i)
123 .copied()
124 .unwrap_or_else(|| {
125 panic!(
126 "missing @pretty mode hint span for `{}` at index {}",
127 hint, i
128 )
129 });
130 diagnostics.push(Diagnostic {
131 range: line_index.span_to_range(span.0, span.1),
132 severity: Some(DiagnosticSeverity::WARNING),
133 source: Some("bbnf".into()),
134 message: format!(
135 "Unknown heuristic mode `{}`. Valid modes: auto, minimal, off",
136 hint
137 ),
138 ..Default::default()
139 });
140 }
141 if let Some(&span) = pretty.hint_spans.get(i) {
143 semantic_tokens.push(SemanticTokenInfo {
144 span,
145 token_type: token_types::KEYWORD,
146 });
147 }
148 }
149 continue;
150 }
151
152 if !defined.contains_key(pretty.rule_name.as_str())
154 && !imported_names.contains(pretty.rule_name.as_str())
155 {
156 diagnostics.push(Diagnostic {
157 range: line_index.span_to_range(
158 pretty.rule_name_span.0,
159 pretty.rule_name_span.1,
160 ),
161 severity: Some(DiagnosticSeverity::WARNING),
162 source: Some("bbnf".into()),
163 message: format!(
164 "`@pretty` targets undefined rule: `{}`",
165 pretty.rule_name
166 ),
167 ..Default::default()
168 });
169 }
170
171 for (i, hint) in pretty.hints.iter().enumerate() {
173 let span = pretty
174 .hint_spans
175 .get(i)
176 .copied()
177 .unwrap_or_else(|| {
178 panic!(
179 "missing @pretty hint span for `{}` at index {}",
180 hint, i
181 )
182 });
183
184 if !is_valid_hint(hint) {
185 let mut msg = format!("Unknown `@pretty` hint: `{}`", hint);
186 if let Some(suggestion) = closest_hint(hint) {
187 msg.push_str(&format!(". Did you mean `{}`?", suggestion));
188 } else {
189 let valid = hints::valid_hint_names();
190 msg.push_str(&format!(". Valid hints: {}", valid.join(", ")));
191 }
192 diagnostics.push(Diagnostic {
193 range: line_index.span_to_range(span.0, span.1),
194 severity: Some(DiagnosticSeverity::WARNING),
195 source: Some("bbnf".into()),
196 message: msg,
197 ..Default::default()
198 });
199 }
200
201 semantic_tokens.push(SemanticTokenInfo {
203 span,
204 token_type: token_types::KEYWORD,
205 });
206 }
207 }
208
209 (diagnostics, semantic_tokens)
210}