Skip to main content

darklua_core/rules/
convert_index_to_field.rs

1use crate::nodes::{
2    Block, Expression, FieldExpression, Identifier, IndexExpression, Prefix, TableEntry,
3    TableExpression, TableFieldEntry, Variable,
4};
5use crate::process::utils::is_valid_identifier;
6use crate::process::{DefaultVisitor, Evaluator, LuaValue, NodeProcessor, NodeVisitor};
7use crate::rules::{
8    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
9};
10
11use super::verify_no_rule_properties;
12
13use std::mem;
14
15#[derive(Debug, Clone, Default)]
16struct Converter {
17    evaluator: Evaluator,
18}
19
20impl Converter {
21    #[inline]
22    fn convert_index_to_field(&self, index: &IndexExpression) -> Option<FieldExpression> {
23        self.convert_to_field(index.get_index())
24            .map(|key| FieldExpression::new(index.get_prefix().clone(), Identifier::new(key)))
25    }
26
27    fn convert_to_field(&self, key_expression: &Expression) -> Option<String> {
28        if let LuaValue::String(string) = self.evaluator.evaluate(key_expression) {
29            String::from_utf8(string)
30                .ok()
31                .filter(|string| is_valid_identifier(string))
32        } else {
33            None
34        }
35    }
36}
37
38impl NodeProcessor for Converter {
39    fn process_expression(&mut self, expression: &mut Expression) {
40        let field: Option<Expression> = if let Expression::Index(index) = expression {
41            self.convert_index_to_field(index).map(Into::into)
42        } else {
43            None
44        };
45        if let Some(mut field) = field {
46            mem::swap(expression, &mut field);
47        }
48    }
49
50    fn process_prefix_expression(&mut self, prefix: &mut Prefix) {
51        let field: Option<Prefix> = if let Prefix::Index(index) = prefix {
52            self.convert_index_to_field(index).map(Into::into)
53        } else {
54            None
55        };
56        if let Some(mut field) = field {
57            mem::swap(prefix, &mut field);
58        }
59    }
60
61    fn process_variable(&mut self, variable: &mut Variable) {
62        let field: Option<Variable> = if let Variable::Index(index) = variable {
63            self.convert_index_to_field(index).map(Into::into)
64        } else {
65            None
66        };
67        if let Some(mut field) = field {
68            mem::swap(variable, &mut field);
69        }
70    }
71
72    fn process_table_expression(&mut self, table: &mut TableExpression) {
73        for entry in table.iter_mut_entries() {
74            let replace_with = match entry {
75                TableEntry::Index(entry) => self
76                    .convert_to_field(entry.get_key())
77                    .map(|key| TableFieldEntry::new(key, entry.get_value().clone()))
78                    .map(TableEntry::from),
79
80                TableEntry::Field(_) | TableEntry::Value(_) => None,
81            };
82            if let Some(new_entry) = replace_with {
83                *entry = new_entry;
84            }
85        }
86    }
87}
88
89pub const CONVERT_INDEX_TO_FIELD_RULE_NAME: &str = "convert_index_to_field";
90
91/// A rule that converts index expression into field expression.
92#[derive(Debug, Default, PartialEq, Eq)]
93pub struct ConvertIndexToField {
94    metadata: RuleMetadata,
95}
96
97impl FlawlessRule for ConvertIndexToField {
98    fn flawless_process(&self, block: &mut Block, _: &Context) {
99        let mut processor = Converter::default();
100        DefaultVisitor::visit_block(block, &mut processor);
101    }
102}
103
104impl RuleConfiguration for ConvertIndexToField {
105    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
106        verify_no_rule_properties(&properties)?;
107
108        Ok(())
109    }
110
111    fn get_name(&self) -> &'static str {
112        CONVERT_INDEX_TO_FIELD_RULE_NAME
113    }
114
115    fn serialize_to_properties(&self) -> RuleProperties {
116        RuleProperties::new()
117    }
118
119    fn set_metadata(&mut self, metadata: RuleMetadata) {
120        self.metadata = metadata;
121    }
122
123    fn metadata(&self) -> &RuleMetadata {
124        &self.metadata
125    }
126}
127
128#[cfg(test)]
129mod test {
130    use super::*;
131    use crate::rules::Rule;
132
133    use insta::assert_json_snapshot;
134
135    fn new_rule() -> ConvertIndexToField {
136        ConvertIndexToField::default()
137    }
138
139    #[test]
140    fn serialize_default_rule() {
141        let rule: Box<dyn Rule> = Box::new(new_rule());
142
143        assert_json_snapshot!(rule, @r###""convert_index_to_field""###);
144    }
145
146    #[test]
147    fn configure_with_extra_field_error() {
148        let result = json5::from_str::<Box<dyn Rule>>(
149            r#"{
150            rule: 'convert_index_to_field',
151            prop: "something",
152        }"#,
153        );
154        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
155    }
156}