1use dotenv_core::LineEntry;
2
3use crate::{LintKind, Warning};
4
5mod duplicated_key;
6mod ending_blank_line;
7mod extra_blank_line;
8mod incorrect_delimiter;
9mod key_without_value;
10mod leading_character;
11mod lowercase_key;
12mod quote_character;
13mod space_character;
14mod substitution_key;
15mod trailing_whitespace;
16mod unordered_key;
17mod value_without_quotes;
18
19trait Fix {
20 fn name(&self) -> LintKind;
21
22 fn fix_warnings(&self, warning_lines: &[usize], lines: &mut Vec<LineEntry>) -> Option<usize> {
23 let mut count: usize = 0;
24 for line_number in warning_lines {
25 let line = lines
26 .iter_mut()
27 .find(|entry| entry.number == *line_number)?;
28 if self.fix_line(line).is_some() {
29 count += 1;
30 }
31 }
32
33 Some(count)
34 }
35
36 fn fix_line(&self, _line: &mut LineEntry) -> Option<()> {
37 None
38 }
39
40 fn is_mandatory(&self) -> bool {
41 false
42 }
43}
44
45fn fixlist() -> Vec<Box<dyn Fix>> {
47 vec![
48 Box::<key_without_value::KeyWithoutValueFixer>::default(),
51 Box::<lowercase_key::LowercaseKeyFixer>::default(),
52 Box::<space_character::SpaceCharacterFixer>::default(),
53 Box::<trailing_whitespace::TrailingWhitespaceFixer>::default(),
54 Box::<leading_character::LeadingCharacterFixer>::default(),
55 Box::<value_without_quotes::ValueWithoutQuotesFixer>::default(),
56 Box::<quote_character::QuoteCharacterFixer>::default(),
57 Box::<incorrect_delimiter::IncorrectDelimiterFixer>::default(),
58 Box::<extra_blank_line::ExtraBlankLineFixer>::default(),
59 Box::<substitution_key::SubstitutionKeyFixer>::default(),
60 Box::<unordered_key::UnorderedKeyFixer>::default(),
62 Box::<duplicated_key::DuplicatedKeyFixer>::default(),
63 Box::<ending_blank_line::EndingBlankLineFixer>::default(),
64 ]
65}
66
67pub fn fix(warnings: &[Warning], lines: &mut Vec<LineEntry>, skip_checks: &[LintKind]) -> usize {
68 if warnings.is_empty() {
69 return 0;
70 }
71 let mut fixes = fixlist();
72
73 fixes.retain(|f| !skip_checks.contains(&f.name()));
75
76 let mut count = 0;
77 for fixer in fixes {
78 let warning_lines: Vec<usize> = warnings
80 .iter()
81 .filter(|w| *w.check_name() == fixer.name())
82 .map(|w| w.line_number())
83 .collect();
84
85 if fixer.is_mandatory() || !warning_lines.is_empty() {
87 match fixer.fix_warnings(&warning_lines, lines) {
88 Some(fixer_count) => count += fixer_count,
89 None => {
90 return 0;
91 }
92 }
93 }
94 }
95
96 lines.retain(|l| !l.is_deleted);
98
99 count
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::tests::*;
106
107 #[test]
108 fn run_with_empty_warnings_test() {
109 let mut lines = vec![line_entry(1, 2, "A=B"), blank_line_entry(2, 2)];
110
111 assert_eq!(0, fix(&[], &mut lines, &[]));
112 }
113
114 #[test]
115 fn run_with_fixable_warning_test() {
116 let mut lines = vec![
117 line_entry(1, 3, "A=B"),
118 line_entry(2, 3, "c=d"),
119 blank_line_entry(3, 3),
120 ];
121 let warnings = [Warning::new(
122 lines[1].number,
123 LintKind::LowercaseKey,
124 "The c key should be in uppercase",
125 )];
126
127 assert_eq!(1, fix(&warnings, &mut lines, &[]));
128 assert_eq!("C=d", lines[1].raw_string);
129 }
130
131 #[test]
132 fn run_when_lines_do_not_fit_numbers_test() {
133 let mut lines = vec![
134 line_entry(1, 3, "a=B"),
135 line_entry(4, 3, "c=D"),
136 blank_line_entry(3, 3),
137 ];
138 let warnings = [
139 Warning::new(
140 lines[0].number,
141 LintKind::LowercaseKey,
142 "The a key should be in uppercase",
143 ),
144 Warning::new(
145 lines[1].number,
146 LintKind::LowercaseKey,
147 "The c key should be in uppercase",
148 ),
149 ];
150
151 assert_eq!(2, fix(&warnings, &mut lines, &[]));
152 }
153
154 #[test]
155 fn new_warnings_after_fix_test() {
156 let mut lines = vec![
157 line_entry(1, 5, "A1=1"),
158 line_entry(2, 5, "A2=2"),
159 line_entry(3, 5, "a0=0"),
160 line_entry(4, 5, "a2=2"),
161 blank_line_entry(5, 5),
162 ];
163 let warnings = [
164 Warning::new(
165 lines[2].number,
166 LintKind::LowercaseKey,
167 "The a0 key should be in uppercase",
168 ),
169 Warning::new(
170 lines[3].number,
171 LintKind::LowercaseKey,
172 "The a2 key should be in uppercase",
173 ),
174 ];
175
176 assert_eq!(2, fix(&warnings, &mut lines, &[]));
177 assert_eq!("A0=0", lines[0].raw_string);
178 assert_eq!("A1=1", lines[1].raw_string);
179 assert_eq!("A2=2", lines[2].raw_string);
180 assert_eq!("# A2=2", lines[3].raw_string);
181 assert_eq!("\n", lines[4].raw_string);
182 }
183
184 #[test]
185 fn skip_duplicated_key() {
186 let mut lines = vec![
187 line_entry(1, 5, "A1=1"),
188 line_entry(2, 5, "A2=2"),
189 line_entry(3, 5, "a0=0"),
190 line_entry(4, 5, "a2=2"),
191 blank_line_entry(5, 5),
192 ];
193 let warnings = [
194 Warning::new(
195 lines[2].number,
196 LintKind::LowercaseKey,
197 "The a0 key should be in uppercase",
198 ),
199 Warning::new(
200 lines[3].number,
201 LintKind::LowercaseKey,
202 "The a2 key should be in uppercase",
203 ),
204 ];
205
206 assert_eq!(2, fix(&warnings, &mut lines, &[LintKind::DuplicatedKey]));
207 assert_eq!("A0=0", lines[0].raw_string);
208 assert_eq!("A1=1", lines[1].raw_string);
209 assert_eq!("A2=2", lines[2].raw_string);
210 assert_eq!("A2=2", lines[3].raw_string);
211 assert_eq!("\n", lines[4].raw_string);
212 }
213
214 #[test]
215 fn skip_unordered_key() {
216 let mut lines = vec![
217 line_entry(1, 5, "A1=1"),
218 line_entry(2, 5, "A2=2"),
219 line_entry(3, 5, "a0=0"),
220 line_entry(4, 5, "a2=2"),
221 blank_line_entry(5, 5),
222 ];
223 let warnings = [
224 Warning::new(
225 lines[2].number,
226 LintKind::LowercaseKey,
227 "The a0 key should be in uppercase",
228 ),
229 Warning::new(
230 lines[3].number,
231 LintKind::LowercaseKey,
232 "The a2 key should be in uppercase",
233 ),
234 ];
235
236 assert_eq!(2, fix(&warnings, &mut lines, &[LintKind::UnorderedKey]));
237 assert_eq!("A1=1", lines[0].raw_string);
238 assert_eq!("A2=2", lines[1].raw_string);
239 assert_eq!("A0=0", lines[2].raw_string);
240 assert_eq!("# A2=2", lines[3].raw_string);
241 assert_eq!("\n", lines[4].raw_string);
242 }
243}