1use std::str::FromStr;
2
3use ahash::AHashMap;
4use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
5
6use crate::core::config::{FluffConfig, Value};
7use crate::utils::reflow::depth_map::{DepthInfo, StackPositionType};
8use crate::utils::reflow::reindent::{IndentUnit, TrailingComments};
9
10type ConfigElementType = AHashMap<String, String>;
11type ConfigDictType = AHashMap<SyntaxKind, ConfigElementType>;
12
13#[derive(Debug, PartialEq, Eq, Clone)]
15pub struct BlockConfig {
16 pub spacing_before: Spacing,
17 pub spacing_after: Spacing,
18 pub spacing_within: Option<Spacing>,
19 pub line_position: Option<&'static str>,
20}
21
22impl Default for BlockConfig {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl BlockConfig {
29 pub fn new() -> Self {
30 BlockConfig {
31 spacing_before: Spacing::Single,
32 spacing_after: Spacing::Single,
33 spacing_within: None,
34 line_position: None,
35 }
36 }
37
38 fn convert_line_position(line_position: &str) -> &'static str {
39 match line_position {
40 "alone" => "alone",
41 "leading" => "leading",
42 "trailing" => "trailing",
43 "alone:strict" => "alone:strict",
44 _ => unreachable!("Expected 'alone', 'leading' found '{}'", line_position),
45 }
46 }
47
48 pub fn incorporate(
50 &mut self,
51 before: Option<Spacing>,
52 after: Option<Spacing>,
53 within: Option<Spacing>,
54 line_position: Option<&'static str>,
55 config: Option<&ConfigElementType>,
56 ) {
57 self.spacing_before = before
58 .or_else(|| {
59 config
60 .and_then(|c| c.get("spacing_before"))
61 .map(|it| it.parse().unwrap())
62 })
63 .unwrap_or(self.spacing_before);
64
65 self.spacing_after = after
66 .or_else(|| {
67 config
68 .and_then(|c| c.get("spacing_after"))
69 .map(|it| it.parse().unwrap())
70 })
71 .unwrap_or(self.spacing_after);
72
73 self.spacing_within = within.or_else(|| {
74 config
75 .and_then(|c| c.get("spacing_within"))
76 .map(|it| it.parse().unwrap())
77 });
78
79 self.line_position = line_position.or_else(|| {
80 config
81 .and_then(|c| c.get("line_position"))
82 .map(|value| Self::convert_line_position(value))
83 });
84 }
85}
86
87#[derive(Debug, Default, PartialEq, Eq, Clone)]
93pub struct ReflowConfig {
94 configs: ConfigDictType,
95 config_types: SyntaxSet,
96 pub(crate) indent_unit: IndentUnit,
100 pub(crate) max_line_length: usize,
101 pub(crate) hanging_indents: bool,
102 pub(crate) allow_implicit_indents: bool,
103 pub(crate) trailing_comments: TrailingComments,
104}
105
106#[derive(Debug, PartialEq, Eq, Clone, Copy)]
107pub enum Spacing {
108 Single,
109 Touch,
110 TouchInline,
111 SingleInline,
112 Any,
113 Align {
114 seg_type: SyntaxKind,
115 within: Option<SyntaxKind>,
116 scope: Option<SyntaxKind>,
117 },
118}
119
120impl FromStr for Spacing {
121 type Err = ();
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 Ok(match s {
125 "single" => Self::Single,
126 "touch" => Self::Touch,
127 "touch:inline" => Self::TouchInline,
128 "single:inline" => Self::SingleInline,
129 "any" => Self::Any,
130 s => {
131 if let Some(rest) = s.strip_prefix("align") {
132 let mut args = rest.split(':');
133 args.next();
134
135 let seg_type = args.next().map(|it| it.parse().unwrap()).unwrap();
136 let within = args.next().map(|it| it.parse().unwrap());
137 let scope = args.next().map(|it| it.parse().unwrap());
138
139 Spacing::Align {
140 seg_type,
141 within,
142 scope,
143 }
144 } else {
145 unimplemented!("{s}")
146 }
147 }
148 })
149 }
150}
151
152impl ReflowConfig {
153 pub fn get_block_config(
154 &self,
155 block_class_types: &SyntaxSet,
156 depth_info: Option<&DepthInfo>,
157 ) -> BlockConfig {
158 let configured_types = block_class_types.clone().intersection(&self.config_types);
159
160 let mut block_config = BlockConfig::new();
161
162 if let Some(depth_info) = depth_info {
163 let (mut parent_start, mut parent_end) = (true, true);
164
165 for (idx, key) in depth_info.stack_hashes.iter().rev().enumerate() {
166 let stack_position = &depth_info.stack_positions[key];
167
168 if !matches!(
169 stack_position.type_,
170 Some(StackPositionType::Solo) | Some(StackPositionType::Start)
171 ) {
172 parent_start = false;
173 }
174
175 if !matches!(
176 stack_position.type_,
177 Some(StackPositionType::Solo) | Some(StackPositionType::End)
178 ) {
179 parent_end = false;
180 }
181
182 if !parent_start && !parent_end {
183 break;
184 }
185
186 let parent_classes =
187 &depth_info.stack_class_types[depth_info.stack_class_types.len() - 1 - idx];
188
189 let configured_parent_types =
190 self.config_types.clone().intersection(parent_classes);
191
192 if parent_start {
193 for seg_type in configured_parent_types.clone() {
194 let before = self
195 .configs
196 .get(&seg_type)
197 .and_then(|conf| conf.get("spacing_before"))
198 .map(|it| it.as_str());
199 let before = before.map(|it| it.parse().unwrap());
200
201 block_config.incorporate(before, None, None, None, None);
202 }
203 }
204
205 if parent_end {
206 for seg_type in configured_parent_types {
207 let after = self
208 .configs
209 .get(&seg_type)
210 .and_then(|conf| conf.get("spacing_after"))
211 .map(|it| it.as_str());
212
213 let after = after.map(|it| it.parse().unwrap());
214 block_config.incorporate(None, after, None, None, None);
215 }
216 }
217 }
218 }
219
220 for seg_type in configured_types {
221 block_config.incorporate(None, None, None, None, self.configs.get(&seg_type));
222 }
223
224 block_config
225 }
226
227 pub fn from_fluff_config(config: &FluffConfig) -> ReflowConfig {
228 let configs = config.raw["layout"]["type"].as_map().unwrap().clone();
229 let config_types = configs
230 .keys()
231 .map(|x| x.parse().unwrap_or_else(|_| unimplemented!("{x}")))
232 .collect::<SyntaxSet>();
233
234 let trailing_comments = config.raw["indentation"]["trailing_comments"]
235 .as_string()
236 .unwrap();
237 let trailing_comments = TrailingComments::from_str(trailing_comments).unwrap();
238
239 let tab_space_size = config.raw["indentation"]["tab_space_size"]
240 .as_int()
241 .unwrap() as usize;
242 let indent_unit = config.raw["indentation"]["indent_unit"]
243 .as_string()
244 .unwrap();
245 let indent_unit = IndentUnit::from_type_and_size(indent_unit, tab_space_size);
246
247 let mut configs = convert_to_config_dict(configs);
248 let keys: Vec<_> = configs.keys().copied().collect();
249
250 for seg_type in keys {
251 for key in ["spacing_before", "spacing_after"] {
252 if configs[&seg_type].get(key).map(String::as_str) == Some("align") {
253 let mut new_key = format!("align:{}", seg_type.as_str());
254 if let Some(align_within) = configs[&seg_type].get("align_within") {
255 new_key.push_str(&format!(":{align_within}"));
256
257 if let Some(align_scope) = configs[&seg_type].get("align_scope") {
258 new_key.push_str(&format!(":{align_scope}"));
259 }
260 }
261
262 *configs.get_mut(&seg_type).unwrap().get_mut(key).unwrap() = new_key;
263 }
264 }
265 }
266
267 ReflowConfig {
268 configs,
269 config_types,
270 indent_unit,
271 max_line_length: config.raw["core"]["max_line_length"].as_int().unwrap() as usize,
272 hanging_indents: config.raw["indentation"]["hanging_indents"]
273 .as_bool()
274 .unwrap_or_default(),
275 allow_implicit_indents: config.raw["indentation"]["allow_implicit_indents"]
276 .as_bool()
277 .unwrap(),
278 trailing_comments,
279 }
280 }
281}
282
283fn convert_to_config_dict(input: AHashMap<String, Value>) -> ConfigDictType {
284 let mut config_dict = ConfigDictType::new();
285
286 for (key, value) in input {
287 match value {
288 Value::Map(map_value) => {
289 let element = map_value
290 .into_iter()
291 .map(|(inner_key, inner_value)| {
292 if let Value::String(value_str) = inner_value {
293 (inner_key, value_str.into())
294 } else {
295 panic!("Expected a Value::String, found another variant.");
296 }
297 })
298 .collect::<ConfigElementType>();
299 config_dict.insert(
300 key.parse().unwrap_or_else(|_| unimplemented!("{key}")),
301 element,
302 );
303 }
304 _ => panic!("Expected a Value::Map, found another variant."),
305 }
306 }
307
308 config_dict
309}