Repository: dotenv-linter/dotenv-linter (rust)

Issue:
ExtraBlankLineFixer with control comments work incorrectly
I found a bug when we run dotenv-linter with control comments(#237) and `--fix` flag (now specific for ExtraBlankLineFixer #260).
This is because the fixer works on all line collection without matching `Warning` line number or re-checking the control comments.


<details>
<summary> Example </summary>


```dotenv
DEBUG_HTTP=true

# dotenv-linter:off ExtraBlankLine



# dotenv-linter:on ExtraBlankLine



DB_NAME=development
```

After run `dotenv-linter -f .env` we will receive next result:

```dotenv
DEBUG_HTTP=true

# dotenv-linter:off ExtraBlankLine

# dotenv-linter:on ExtraBlankLine

DB_NAME=development
```

With output:
```
Fixed warnings:
../../workdir/test/test.env:9 ExtraBlankLine: Extra blank line detected
../../workdir/test/test.env:10 ExtraBlankLine: Extra blank line detected
```
</details>

Ie also fixed warnings in the comment block.


**Simple solution:** in such cases, check control comments additionally.
Perhaps it makes sense to move some of the code into a module.

Or we need to **use a different approach** in correcting and matching, but after other fixers, the line order may change, and changing the line number in `Warning` does not look the best way - this way we will not be able to display the numbers of the original file (unless everything is fixed and the output is needed)
Or, as has already been suggested, run them one after another. But all this **requires more changes.**


Now it is also relevant for #261.

Any suggestions? :thinking: 

Pull request:
Fix a bug with ExtraBlankLineFixer and control comments
<!--
Thank you for your pull request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.
-->

<!-- _Please make sure to review and check all of these items:_ -->

#### ✔ Checklist:
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] This PR has been added to [CHANGELOG.md](https://github.com/dotenv-linter/dotenv-linter/blob/master/CHANGELOG.md) (at the top of the list);
- [x] Tests for the changes have been added (for bug fixes / features).

<!-- _NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open._ -->


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e3195e..3aff246 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ ### 🚀 Added
 - Add installation CI test for Windows (via `install.sh`) [#235](https://github.com/dotenv-linter/dotenv-linter/pull/235) ([@DDtKey](https://github.com/DDtKey))
 
 ### 🔧 Changed
+- Fix a bug with `ExtraBlankLineFixer` and control comments [#279](https://github.com/dotenv-linter/dotenv-linter/pull/279) ([@mgrachev](https://github.com/mgrachev))
 - Move logic for creating `LineEntry` for tests to `common` module [#280](https://github.com/dotenv-linter/dotenv-linter/pull/280) ([@mgrachev](https://github.com/mgrachev))
 - Simplify UnorderedKeyChecker [#277](https://github.com/dotenv-linter/dotenv-linter/pull/277) ([@mgrachev](https://github.com/mgrachev))
 - Partition fixed/unfixed warnings [#275](https://github.com/dotenv-linter/dotenv-linter/pull/275) ([@gillespiecd](https://github.com/gillespiecd))
diff --git a/src/common.rs b/src/common.rs
index be4eea0..8736d2d 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -39,6 +39,7 @@ pub fn blank_line_entry(number: usize, total_lines: usize) -> LineEntry {
                 total_lines,
             },
             raw_string: String::from("\n"),
+            is_deleted: false,
         }
     }
 
@@ -52,6 +53,7 @@ pub fn line_entry(number: usize, total_lines: usize, raw_string: &str) -> LineEn
                 total_lines,
             },
             raw_string: String::from(raw_string),
+            is_deleted: false,
         }
     }
 }
diff --git a/src/common/line_entry.rs b/src/common/line_entry.rs
index bae21cd..f7828dc 100644
--- a/src/common/line_entry.rs
+++ b/src/common/line_entry.rs
@@ -5,6 +5,9 @@ pub struct LineEntry {
     pub number: usize,
     pub file: FileEntry,
     pub raw_string: String,
+
+    /// Used in ExtraBlankLineFixer
+    pub is_deleted: bool,
 }
 
 impl LineEntry {
@@ -49,6 +52,10 @@ pub fn trimmed_string(&self) -> &str {
     pub fn is_last_line(&self) -> bool {
         self.file.total_lines == self.number
     }
+
+    pub fn mark_as_deleted(&mut self) {
+        self.is_deleted = true;
+    }
 }
 
 #[cfg(test)]
diff --git a/src/fixes.rs b/src/fixes.rs
index aa92c5d..0a9c80d 100644
--- a/src/fixes.rs
+++ b/src/fixes.rs
@@ -15,7 +15,7 @@ trait Fix {
     fn name(&self) -> &str;
 
     fn fix_warnings(
-        &self,
+        &mut self,
         warnings: Vec<&mut Warning>,
         lines: &mut Vec<LineEntry>,
     ) -> Option<usize> {
@@ -31,7 +31,7 @@ fn fix_warnings(
         Some(count)
     }
 
-    fn fix_line(&self, _line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, _line: &mut LineEntry) -> Option<()> {
         None
     }
 }
@@ -48,11 +48,12 @@ fn fixlist() -> Vec<Box<dyn Fix>> {
         Box::new(leading_character::LeadingCharacterFixer::default()),
         Box::new(quote_character::QuoteCharacterFixer::default()),
         Box::new(incorrect_delimiter::IncorrectDelimiterFixer::default()),
+        Box::new(duplicated_key::DuplicatedKeyFixer::default()),
         // Then we should run the fixers that handle the line entry collection at whole.
         // And at the end we should run the fixer for ExtraBlankLine check (because the previous
         // fixers can create additional extra blank lines).
         Box::new(extra_blank_line::ExtraBlankLineFixer::default()),
-        Box::new(duplicated_key::DuplicatedKeyFixer::default()),
+        // UnorderedKeyFixer should be here
         Box::new(ending_blank_line::EndingBlankLineFixer::default()),
     ]
 }
@@ -63,7 +64,7 @@ pub fn run(warnings: &mut [Warning], lines: &mut Vec<LineEntry>) -> usize {
     }
 
     let mut count = 0;
-    for fixer in fixlist() {
+    for mut fixer in fixlist() {
         // We can optimize it: create check_name:warnings map in advance
         let fixer_warnings: Vec<&mut Warning> = warnings
             .iter_mut()
@@ -83,6 +84,9 @@ pub fn run(warnings: &mut [Warning], lines: &mut Vec<LineEntry>) -> usize {
         }
     }
 
+    // Removes extra blank lines
+    lines.retain(|l| !l.is_deleted);
+
     count
 }
 
@@ -167,7 +171,7 @@ fn name(&self) -> &str {
             self.name
         }
 
-        fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+        fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
             if line.raw_string.chars().count() > 5 {
                 Some(())
             } else {
@@ -182,7 +186,7 @@ fn warnings_are_marked_as_fixed_if_fix_returns_some() {
 
         let mut warning = Warning::new(lines[0].clone(), "", String::from(""));
 
-        let fixer = TestFixer { name: "fixer" };
+        let mut fixer = TestFixer { name: "fixer" };
 
         fixer.fix_warnings(vec![&mut warning], &mut lines);
 
@@ -195,7 +199,7 @@ fn warnings_are_not_marked_as_fixed_if_fix_returns_none() {
 
         let mut warning = Warning::new(lines[0].clone(), "", String::from(""));
 
-        let fixer = TestFixer { name: "fixer" };
+        let mut fixer = TestFixer { name: "fixer" };
 
         fixer.fix_warnings(vec![&mut warning], &mut lines);
 
diff --git a/src/fixes/duplicated_key.rs b/src/fixes/duplicated_key.rs
index 4cb1509..428877e 100644
--- a/src/fixes/duplicated_key.rs
+++ b/src/fixes/duplicated_key.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         line.raw_string = format!("# {}", line.raw_string);
 
         Some(())
@@ -32,7 +32,7 @@ mod tests {
 
     #[test]
     fn fix_warnings() {
-        let fixer = DuplicatedKeyFixer::default();
+        let mut fixer = DuplicatedKeyFixer::default();
         let mut lines = vec![
             line_entry(1, 4, "FOO=BAR"),
             line_entry(2, 4, "Z=Y"),
diff --git a/src/fixes/ending_blank_line.rs b/src/fixes/ending_blank_line.rs
index 45361b7..c41c1c7 100644
--- a/src/fixes/ending_blank_line.rs
+++ b/src/fixes/ending_blank_line.rs
@@ -19,7 +19,7 @@ fn name(&self) -> &str {
     }
 
     fn fix_warnings(
-        &self,
+        &mut self,
         warnings: Vec<&mut Warning>,
         lines: &mut Vec<LineEntry>,
     ) -> Option<usize> {
@@ -31,6 +31,7 @@ fn fix_warnings(
                 number: lines.len() + 1,
                 file,
                 raw_string: LF.to_string(),
+                is_deleted: false,
             });
 
             for warning in warnings {
@@ -51,7 +52,7 @@ mod tests {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = EndingBlankLineFixer::default();
+        let mut fixer = EndingBlankLineFixer::default();
         let mut lines = vec![line_entry(1, 2, "FOO=BAR"), line_entry(2, 2, "Z=Y")];
         let mut warning = Warning::new(
             lines[1].clone(),
@@ -66,7 +67,7 @@ fn fix_warnings_test() {
 
     #[test]
     fn ending_blank_line_exist_test() {
-        let fixer = EndingBlankLineFixer::default();
+        let mut fixer = EndingBlankLineFixer::default();
         let mut lines = vec![line_entry(1, 2, "FOO=BAR"), line_entry(2, 2, LF)];
 
         assert_eq!(Some(0), fixer.fix_warnings(vec![], &mut lines));
diff --git a/src/fixes/extra_blank_line.rs b/src/fixes/extra_blank_line.rs
index de99bfb..027c818 100644
--- a/src/fixes/extra_blank_line.rs
+++ b/src/fixes/extra_blank_line.rs
@@ -18,22 +18,9 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_warnings(
-        &self,
-        warnings: Vec<&mut Warning>,
-        lines: &mut Vec<LineEntry>,
-    ) -> Option<usize> {
-        let warning_count = warnings.len();
-
-        // check and remove all blank lines.
-        lines.dedup_by(|a, b| a.is_empty() && b.is_empty());
-
-        // mark as fixed
-        for warning in warnings {
-            warning.mark_as_fixed();
-        }
-
-        Some(warning_count)
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
+        line.mark_as_deleted();
+        Some(())
     }
 }
 
@@ -44,7 +31,7 @@ mod tests {
 
     #[test]
     fn no_blank_lines_test() {
-        let fixer = ExtraBlankLineFixer::default();
+        let mut fixer = ExtraBlankLineFixer::default();
 
         let warnings = vec![];
         let lines = vec![
@@ -60,7 +47,7 @@ fn no_blank_lines_test() {
 
     #[test]
     fn fix_one_extra_blank_line_test() {
-        let fixer = ExtraBlankLineFixer::default();
+        let mut fixer = ExtraBlankLineFixer::default();
 
         let line1 = line_entry(1, 4, "FOO=BAR");
         let line2 = line_entry(2, 4, "");
@@ -79,7 +66,7 @@ fn fix_one_extra_blank_line_test() {
 
     #[test]
     fn fix_multiple_blank_lines_test() {
-        let fixer = ExtraBlankLineFixer::default();
+        let mut fixer = ExtraBlankLineFixer::default();
 
         let line1 = line_entry(1, 5, "FOO=BAR");
         let line2 = line_entry(2, 5, "");
diff --git a/src/fixes/incorrect_delimiter.rs b/src/fixes/incorrect_delimiter.rs
index 7cd970a..df139c0 100644
--- a/src/fixes/incorrect_delimiter.rs
+++ b/src/fixes/incorrect_delimiter.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         let key = line.get_key()?;
 
         let cleaned_key = remove_invalid_leading_chars(&key);
@@ -45,7 +45,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = IncorrectDelimiterFixer::default();
+        let mut fixer = IncorrectDelimiterFixer::default();
         let mut line = line_entry(1, 1, "RAILS-ENV=development");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -54,7 +54,7 @@ fn fix_line_test() {
 
     #[test]
     fn fix_line_with_invalid_prefix_test() {
-        let fixer = IncorrectDelimiterFixer::default();
+        let mut fixer = IncorrectDelimiterFixer::default();
         let mut line = line_entry(1, 1, "**RAILS-ENV=development");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -63,7 +63,7 @@ fn fix_line_with_invalid_prefix_test() {
 
     #[test]
     fn fix_line_with_invalid_suffix_test() {
-        let fixer = IncorrectDelimiterFixer::default();
+        let mut fixer = IncorrectDelimiterFixer::default();
         let mut line = line_entry(1, 1, "RAILS-ENV--=development");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -72,7 +72,7 @@ fn fix_line_with_invalid_suffix_test() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = IncorrectDelimiterFixer::default();
+        let mut fixer = IncorrectDelimiterFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "RAILS-ENV=development"),
             line_entry(2, 3, "RAILS_ENV=true"),
diff --git a/src/fixes/key_without_value.rs b/src/fixes/key_without_value.rs
index 1b7e472..6793e62 100644
--- a/src/fixes/key_without_value.rs
+++ b/src/fixes/key_without_value.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         line.raw_string.push('=');
 
         Some(())
@@ -32,7 +32,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = KeyWithoutValueFixer::default();
+        let mut fixer = KeyWithoutValueFixer::default();
         let mut line = line_entry(1, 1, "FOO");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -41,7 +41,7 @@ fn fix_line_test() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = KeyWithoutValueFixer::default();
+        let mut fixer = KeyWithoutValueFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "FOO"),
             line_entry(2, 3, "Z=Y"),
diff --git a/src/fixes/leading_character.rs b/src/fixes/leading_character.rs
index 82f4b07..f70504b 100644
--- a/src/fixes/leading_character.rs
+++ b/src/fixes/leading_character.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         let key = line.get_key()?;
 
         let cleaned_key = remove_invalid_leading_chars(&key);
@@ -36,7 +36,7 @@ mod tests {
 
     #[test]
     fn fix_leading_dot() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_period = line_entry(1, 1, ".FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_period));
@@ -45,7 +45,7 @@ fn fix_leading_dot() {
 
     #[test]
     fn fix_leading_space() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_space = line_entry(1, 1, " FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_space));
@@ -54,7 +54,7 @@ fn fix_leading_space() {
 
     #[test]
     fn fix_leading_asterisk() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_asterisk = line_entry(1, 1, "*FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_asterisk));
@@ -63,7 +63,7 @@ fn fix_leading_asterisk() {
 
     #[test]
     fn fix_leading_number() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_number = line_entry(1, 1, "1FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_number));
@@ -72,7 +72,7 @@ fn fix_leading_number() {
 
     #[test]
     fn fix_many_invalid_leading_chars() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_number = line_entry(1, 1, "-1&*FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_number));
@@ -81,7 +81,7 @@ fn fix_many_invalid_leading_chars() {
 
     #[test]
     fn leading_underscore_is_unchanged() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut leading_underscore = line_entry(1, 1, "_FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut leading_underscore));
@@ -90,7 +90,7 @@ fn leading_underscore_is_unchanged() {
 
     #[test]
     fn no_leading_char_is_unchanged() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut normal = line_entry(1, 1, "FOO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut normal));
@@ -99,7 +99,7 @@ fn no_leading_char_is_unchanged() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = LeadingCharacterFixer::default();
+        let mut fixer = LeadingCharacterFixer::default();
         let mut lines = vec![
             line_entry(1, 7, ".FOO=BAR"),
             line_entry(2, 7, " Z=Y"),
diff --git a/src/fixes/lowercase_key.rs b/src/fixes/lowercase_key.rs
index 4a84707..45cab18 100644
--- a/src/fixes/lowercase_key.rs
+++ b/src/fixes/lowercase_key.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         let key = line.get_key()?;
         let key = key.to_uppercase();
         line.raw_string = format!("{}={}", key, line.get_value()?);
@@ -34,7 +34,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = LowercaseKeyFixer::default();
+        let mut fixer = LowercaseKeyFixer::default();
         let mut line = line_entry(1, 1, "foO=BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -43,7 +43,7 @@ fn fix_line_test() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = LowercaseKeyFixer::default();
+        let mut fixer = LowercaseKeyFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "foO=BAR"),
             line_entry(2, 3, "Z=Y"),
diff --git a/src/fixes/quote_character.rs b/src/fixes/quote_character.rs
index ba6f49c..e8492dc 100644
--- a/src/fixes/quote_character.rs
+++ b/src/fixes/quote_character.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         let value = line.get_value()?;
         let pure_val = value.replace("'", "").replace("\"", "");
 
@@ -35,7 +35,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = QuoteCharacterFixer::default();
+        let mut fixer = QuoteCharacterFixer::default();
         let mut line = line_entry(1, 1, "FOO=\'\"b\'\"ar\"\'");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -44,7 +44,7 @@ fn fix_line_test() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = QuoteCharacterFixer::default();
+        let mut fixer = QuoteCharacterFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "FOO=\"bar\'\""),
             line_entry(2, 3, "Z=Y"),
diff --git a/src/fixes/space_character.rs b/src/fixes/space_character.rs
index b927e19..afa48cc 100644
--- a/src/fixes/space_character.rs
+++ b/src/fixes/space_character.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         let key = line.get_key()?;
         let value = line.get_value()?;
         line.raw_string = format!("{}={}", key.trim_end(), value.trim_start());
@@ -34,7 +34,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = SpaceCharacterFixer::default();
+        let mut fixer = SpaceCharacterFixer::default();
         let mut line = line_entry(1, 1, "FOO = BAR");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -43,7 +43,7 @@ fn fix_line_test() {
 
     #[test]
     fn trailing_should_not_be_fixed() {
-        let fixer = SpaceCharacterFixer::default();
+        let mut fixer = SpaceCharacterFixer::default();
         let mut line = line_entry(1, 1, "DEBUG_HTTP=true ");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -52,7 +52,7 @@ fn trailing_should_not_be_fixed() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = SpaceCharacterFixer::default();
+        let mut fixer = SpaceCharacterFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "FOO= BAR"),
             line_entry(2, 3, "Z =Y"),
diff --git a/src/fixes/trailing_whitespace.rs b/src/fixes/trailing_whitespace.rs
index 2b2354f..6f49a89 100644
--- a/src/fixes/trailing_whitespace.rs
+++ b/src/fixes/trailing_whitespace.rs
@@ -18,7 +18,7 @@ fn name(&self) -> &str {
         self.name
     }
 
-    fn fix_line(&self, line: &mut LineEntry) -> Option<()> {
+    fn fix_line(&mut self, line: &mut LineEntry) -> Option<()> {
         line.raw_string = line.raw_string.trim_end().to_string();
 
         Some(())
@@ -32,7 +32,7 @@ mod tests {
 
     #[test]
     fn fix_line_test() {
-        let fixer = TrailingWhitespaceFixer::default();
+        let mut fixer = TrailingWhitespaceFixer::default();
         let mut line = line_entry(1, 1, "DEBUG_HTTP=true  ");
 
         assert_eq!(Some(()), fixer.fix_line(&mut line));
@@ -41,7 +41,7 @@ fn fix_line_test() {
 
     #[test]
     fn fix_warnings_test() {
-        let fixer = TrailingWhitespaceFixer::default();
+        let mut fixer = TrailingWhitespaceFixer::default();
         let mut lines = vec![
             line_entry(1, 3, "FOO=BAR "),
             line_entry(2, 3, "Z=Y"),
diff --git a/src/fs_utils.rs b/src/fs_utils.rs
index 8ba1b32..74dbe05 100644
--- a/src/fs_utils.rs
+++ b/src/fs_utils.rs
@@ -180,21 +180,9 @@ fn backup_file_test() {
         };
 
         let lines = vec![
-            LineEntry {
-                number: 1,
-                file: fe.clone(),
-                raw_string: String::from("A=B"),
-            },
-            LineEntry {
-                number: 2,
-                file: fe.clone(),
-                raw_string: String::from("Z=Y"),
-            },
-            LineEntry {
-                number: 3,
-                file: fe.clone(),
-                raw_string: String::from("\n"),
-            },
+            line_entry(1, 3, "A=B"),
+            line_entry(2, 3, "Z=Y"),
+            blank_line_entry(3, 3),
         ];
 
         if write_file(&fe.path, lines).is_ok() {
diff --git a/src/lib.rs b/src/lib.rs
index 0dfc6ef..0bafd1f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -112,6 +112,7 @@ fn get_line_entries(fe: &FileEntry, lines: Vec<String>) -> Vec<LineEntry> {
             number: index + 1,
             file: fe.clone(),
             raw_string: line,
+            is_deleted: false,
         })
     }
 


Test patch:
diff --git a/tests/fixes/extra_blank_line.rs b/tests/fixes/extra_blank_line.rs
index dcf9078..c595577 100644
--- a/tests/fixes/extra_blank_line.rs
+++ b/tests/fixes/extra_blank_line.rs
@@ -14,3 +14,22 @@ fn extra_blank_line() {
 
     testdir.close();
 }
+
+#[test]
+fn extra_blank_line_with_control_comments() {
+    let testdir = TestDir::new();
+    let testfile = testdir.create_testfile(
+        ".env",
+        "FOO=BAR\n\n# dotenv-linter:off ExtraBlankLine\n\n\n# dotenv-linter:on ExtraBlankLine\nBAR=FOO\n\n\n",
+    );
+    let expected_output = String::from(
+        "Fixed warnings:\n\
+        .env:9 ExtraBlankLine: Extra blank line detected\n\
+        .env:10 ExtraBlankLine: Extra blank line detected\n",
+    );
+    testdir.test_command_fix_success(expected_output);
+
+    assert_eq!(testfile.contents().as_str(), "FOO=BAR\n\n# dotenv-linter:off ExtraBlankLine\n\n\n# dotenv-linter:on ExtraBlankLine\nBAR=FOO\n");
+
+    testdir.close();
+}