use std::str::FromStr;
use ahash::AHashMap;
use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
use crate::core::config::{FluffConfig, Value};
use crate::utils::reflow::depth_map::{DepthInfo, StackPositionType};
use crate::utils::reflow::reindent::{IndentUnit, TrailingComments};
type ConfigElementType = AHashMap<String, String>;
type ConfigDictType = AHashMap<SyntaxKind, ConfigElementType>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BlockConfig {
pub spacing_before: Spacing,
pub spacing_after: Spacing,
pub spacing_within: Option<Spacing>,
pub line_position: Option<&'static str>,
}
impl Default for BlockConfig {
fn default() -> Self {
Self::new()
}
}
impl BlockConfig {
pub fn new() -> Self {
BlockConfig {
spacing_before: Spacing::Single,
spacing_after: Spacing::Single,
spacing_within: None,
line_position: None,
}
}
fn convert_line_position(line_position: &str) -> &'static str {
match line_position {
"alone" => "alone",
"leading" => "leading",
"trailing" => "trailing",
"alone:strict" => "alone:strict",
_ => unreachable!("Expected 'alone', 'leading' found '{}'", line_position),
}
}
pub fn incorporate(
&mut self,
before: Option<Spacing>,
after: Option<Spacing>,
within: Option<Spacing>,
line_position: Option<&'static str>,
config: Option<&ConfigElementType>,
) {
let empty = AHashMap::new();
let config = config.unwrap_or(&empty);
self.spacing_before = before
.or_else(|| config.get("spacing_before").map(|it| it.parse().unwrap()))
.unwrap_or(self.spacing_before);
self.spacing_after = after
.or_else(|| config.get("spacing_after").map(|it| it.parse().unwrap()))
.unwrap_or(self.spacing_after);
self.spacing_within =
within.or_else(|| config.get("spacing_within").map(|it| it.parse().unwrap()));
self.line_position = line_position.or_else(|| {
let line_position = config.get("line_position");
match line_position {
Some(value) => Some(Self::convert_line_position(value)),
None => None,
}
});
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct ReflowConfig {
configs: ConfigDictType,
config_types: SyntaxSet,
pub(crate) indent_unit: IndentUnit,
pub(crate) max_line_length: usize,
pub(crate) hanging_indents: bool,
pub(crate) allow_implicit_indents: bool,
pub(crate) trailing_comments: TrailingComments,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Spacing {
Single,
Touch,
TouchInline,
SingleInline,
Any,
Align {
seg_type: SyntaxKind,
within: Option<SyntaxKind>,
scope: Option<SyntaxKind>,
},
}
impl FromStr for Spacing {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"single" => Self::Single,
"touch" => Self::Touch,
"touch:inline" => Self::TouchInline,
"single:inline" => Self::SingleInline,
"any" => Self::Any,
s => {
if let Some(rest) = s.strip_prefix("align") {
let mut args = rest.split(':');
args.next();
let seg_type = args.next().map(|it| it.parse().unwrap()).unwrap();
let within = args.next().map(|it| it.parse().unwrap());
let scope = args.next().map(|it| it.parse().unwrap());
Spacing::Align {
seg_type,
within,
scope,
}
} else {
unimplemented!("{s}")
}
}
})
}
}
impl ReflowConfig {
pub fn get_block_config(
&self,
block_class_types: &SyntaxSet,
depth_info: Option<&DepthInfo>,
) -> BlockConfig {
let configured_types = block_class_types.clone().intersection(&self.config_types);
let mut block_config = BlockConfig::new();
if let Some(depth_info) = depth_info {
let (mut parent_start, mut parent_end) = (true, true);
for (idx, key) in depth_info.stack_hashes.iter().rev().enumerate() {
let stack_position = &depth_info.stack_positions[key];
if !matches!(
stack_position.type_,
Some(StackPositionType::Solo) | Some(StackPositionType::Start)
) {
parent_start = false;
}
if !matches!(
stack_position.type_,
Some(StackPositionType::Solo) | Some(StackPositionType::End)
) {
parent_end = false;
}
if !parent_start && !parent_end {
break;
}
let parent_classes =
&depth_info.stack_class_types[depth_info.stack_class_types.len() - 1 - idx];
let configured_parent_types =
self.config_types.clone().intersection(parent_classes);
if parent_start {
for seg_type in configured_parent_types.clone() {
let before = self
.configs
.get(&seg_type)
.and_then(|conf| conf.get("spacing_before"))
.map(|it| it.as_str());
let before = before.map(|it| it.parse().unwrap());
block_config.incorporate(before, None, None, None, None);
}
}
if parent_end {
for seg_type in configured_parent_types {
let after = self
.configs
.get(&seg_type)
.and_then(|conf| conf.get("spacing_after"))
.map(|it| it.as_str());
let after = after.map(|it| it.parse().unwrap());
block_config.incorporate(None, after, None, None, None);
}
}
}
}
for seg_type in configured_types {
block_config.incorporate(None, None, None, None, self.configs.get(&seg_type));
}
block_config
}
pub fn from_fluff_config(config: &FluffConfig) -> ReflowConfig {
let configs = config.raw["layout"]["type"].as_map().unwrap().clone();
let config_types = configs
.keys()
.map(|x| x.parse().unwrap_or_else(|_| unimplemented!("{x}")))
.collect::<SyntaxSet>();
let trailing_comments = config.raw["indentation"]["trailing_comments"]
.as_string()
.unwrap();
let trailing_comments = TrailingComments::from_str(trailing_comments).unwrap();
let tab_space_size = config.raw["indentation"]["tab_space_size"]
.as_int()
.unwrap() as usize;
let indent_unit = config.raw["indentation"]["indent_unit"]
.as_string()
.unwrap();
let indent_unit = IndentUnit::from_type_and_size(indent_unit, tab_space_size);
let mut configs = convert_to_config_dict(configs);
let keys: Vec<_> = configs.keys().copied().collect();
for seg_type in keys {
for key in ["spacing_before", "spacing_after"] {
if configs[&seg_type].get(key).map(String::as_str) == Some("align") {
let mut new_key = format!("align:{}", seg_type.as_str());
if let Some(align_within) = configs[&seg_type].get("align_within") {
new_key.push_str(&format!(":{align_within}"));
if let Some(align_scope) = configs[&seg_type].get("align_scope") {
new_key.push_str(&format!(":{align_scope}"));
}
}
*configs.get_mut(&seg_type).unwrap().get_mut(key).unwrap() = new_key;
}
}
}
ReflowConfig {
configs,
config_types,
indent_unit,
max_line_length: config.raw["core"]["max_line_length"].as_int().unwrap() as usize,
hanging_indents: config.raw["indentation"]["hanging_indents"]
.as_bool()
.unwrap_or_default(),
allow_implicit_indents: config.raw["indentation"]["allow_implicit_indents"]
.as_bool()
.unwrap(),
trailing_comments,
}
}
}
fn convert_to_config_dict(input: AHashMap<String, Value>) -> ConfigDictType {
let mut config_dict = ConfigDictType::new();
for (key, value) in input {
match value {
Value::Map(map_value) => {
let element = map_value
.into_iter()
.map(|(inner_key, inner_value)| {
if let Value::String(value_str) = inner_value {
(inner_key, value_str.into())
} else {
panic!("Expected a Value::String, found another variant.");
}
})
.collect::<ConfigElementType>();
config_dict.insert(
key.parse().unwrap_or_else(|_| unimplemented!("{key}")),
element,
);
}
_ => panic!("Expected a Value::Map, found another variant."),
}
}
config_dict
}