use std::collections::HashMap;
use ktrs_editorconfig::{Property, PropertyType};
use crate::editorconfig::code_style::{CODE_STYLE_PROPERTY, CodeStyleValue};
use crate::editorconfig::editor_config_property::{
EditorConfigProperty, PropertyRef, PropertyValueType, to_property_with_value,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EditorConfig {
properties: Vec<Property>,
}
impl EditorConfig {
pub fn new(properties: Vec<Property>) -> EditorConfig {
let mut editor_config = EditorConfig::default();
properties.into_iter().for_each(|p| editor_config.put(p));
editor_config
}
fn put(&mut self, property: Property) {
match self
.properties
.iter_mut()
.find(|p| p.name() == property.name())
{
Some(existing) => *existing = property,
None => self.properties.push(property),
}
}
fn property(&self, name: &str) -> Option<&Property> {
self.properties.iter().find(|p| p.name() == name)
}
fn code_style(&self) -> CodeStyleValue {
self.get_property_value(CODE_STYLE_PROPERTY.type_, CODE_STYLE_PROPERTY.type_.name)
.unwrap_or(CODE_STYLE_PROPERTY.default_value)
}
pub fn get<T: PropertyValueType>(&self, editor_config_property: &EditorConfigProperty<T>) -> T {
if let Some(deprecation_error) = editor_config_property.deprecation_error {
panic!(
"DeprecatedEditorConfigPropertyException: Property '{}' is disallowed: {deprecation_error}",
editor_config_property.name
);
}
let property = self.property(&editor_config_property.name).unwrap_or_else(|| {
panic!(
"IllegalStateException: Property '{}' can not be retrieved from this EditorConfig. Note that the EditorConfig which \
is provided to class 'io.github.ktlint.core.rule.engine.core.api.RuleV2' only contains the properties which are \
defined in the property 'usesEditorConfigProperties'.",
editor_config_property.name
)
});
if let Some(new_value) = editor_config_property
.property_mapper
.and_then(|mapper| mapper(Some(property), self.code_style()))
{
return new_value;
}
if property.is_unset() {
editor_config_property
.default_value_for(self.code_style())
.clone()
} else {
self.get_parsed_value_or_default(editor_config_property)
.unwrap_or_else(|| {
editor_config_property
.default_value_for(self.code_style())
.clone()
})
}
}
pub fn get_editor_config_value_or_null<T>(
&self,
property_type: &PropertyType<T>,
property_name: &str,
) -> Option<T> {
self.get_property_value(property_type, property_name)
}
pub fn contains(&self, property_name: &str) -> bool {
self.property(property_name).is_some()
}
fn get_parsed_value_or_default<T: PropertyValueType>(
&self,
p: &EditorConfigProperty<T>,
) -> Option<T> {
let property_value = p
.type_
.parse(self.property(p.type_.name).and_then(Property::source_value));
if property_value.is_valid() {
property_value.into_parsed()
} else {
Some(p.default_value.clone())
}
}
fn get_property_value<T>(
&self,
property_type: &PropertyType<T>,
property_name: &str,
) -> Option<T> {
property_type
.parse(
self.property(property_name)
.and_then(Property::source_value),
)
.into_parsed()
}
pub fn map<T>(&self, mapper: impl FnMut(&Property) -> T) -> Vec<T> {
self.properties.iter().map(mapper).collect()
}
pub fn add_properties_with_default_value_if_missing(
&self,
additional: &[PropertyRef],
) -> EditorConfig {
let mut missing: Vec<&PropertyRef> = Vec::new();
for p in additional.iter().filter(|p| !self.contains(p.name())) {
if !missing.iter().any(|m| m.identity() == p.identity()) {
missing.push(p);
}
}
require_singular_identities(&missing);
let code_style = self.code_style();
let mut properties: Vec<Property> = missing
.iter()
.map(|p| to_property_with_value(&***p, &p.write_default_value(code_style)))
.collect();
properties.extend(self.properties.iter().cloned());
EditorConfig::new(properties)
}
pub fn filter_by(&self, additional: &[PropertyRef]) -> EditorConfig {
self.add_properties_with_default_value_if_missing(additional)
}
}
fn require_singular_identities(editor_config_properties: &[&PropertyRef]) {
let mut by_name: HashMap<&str, Vec<&PropertyRef>> = HashMap::new();
editor_config_properties
.iter()
.for_each(|p| by_name.entry(p.name()).or_default().push(p));
let mut clashes: Vec<String> = by_name
.into_iter()
.filter(|(_, properties)| properties.len() > 1)
.map(|(name, properties)| {
let list: Vec<String> = properties.iter().map(|p| format!(" - {p:?}")).collect();
format!("Found multiple editorconfig properties with name '{name}' but having distinct identities:\n{}", list.join("\n"))
})
.collect();
clashes.sort();
assert!(
clashes.is_empty(),
"IllegalArgumentException: {}",
clashes.join("\n")
);
}