Repository: PlasmaFAIR/fortitude (language: python)

Issue:
Add fix for `deprecated-relational-operators`
Plus a helper function to make future fixes even easier


Discussion / hints:

Fix patch:
diff --git a/fortitude/src/ast.rs b/fortitude/src/ast.rs
index 94bf575..a9cf8a9 100644
--- a/fortitude/src/ast.rs
+++ b/fortitude/src/ast.rs
@@ -106,6 +106,9 @@ pub trait FortitudeNode<'tree> {
 
     /// Return the edit required to remove this node.
     fn edit_delete(&self, source_file: &SourceFile) -> Edit;
+
+    /// Creates an edit that inserts `content`, replacing the whole node
+    fn edit_replacement(&self, content: String) -> Edit;
 }
 
 impl FortitudeNode<'_> for Node<'_> {
@@ -178,6 +181,14 @@ impl FortitudeNode<'_> for Node<'_> {
             Edit::range_deletion(TextRange::new(start_byte, end_byte))
         }
     }
+
+    fn edit_replacement(&self, content: String) -> Edit {
+        Edit::replacement(
+            content,
+            TextSize::try_from(self.start_byte()).unwrap(),
+            TextSize::try_from(self.end_byte()).unwrap(),
+        )
+    }
 }
 
 /// Strip line breaks from a string of Fortran code.
diff --git a/fortitude/src/rules/style/old_style_array_literal.rs b/fortitude/src/rules/style/old_style_array_literal.rs
index 9b5e9a7..0d368b7 100644
--- a/fortitude/src/rules/style/old_style_array_literal.rs
+++ b/fortitude/src/rules/style/old_style_array_literal.rs
@@ -1,7 +1,7 @@
 use crate::ast::FortitudeNode;
 use crate::settings::Settings;
 use crate::{AstRule, FromAstNode};
-use ruff_diagnostics::{Diagnostic, Violation};
+use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
 use ruff_macros::{derive_message_formats, violation};
 use ruff_source_file::SourceFile;
 use tree_sitter::Node;
@@ -16,16 +16,29 @@ use tree_sitter::Node;
 #[violation]
 pub struct OldStyleArrayLiteral {}
 
-impl Violation for OldStyleArrayLiteral {
+impl AlwaysFixableViolation for OldStyleArrayLiteral {
     #[derive_message_formats]
     fn message(&self) -> String {
         format!("Array literal uses old-style syntax: prefer `[...]`")
     }
+
+    fn fix_title(&self) -> String {
+        "Change `(/.../)` to `[...]`".to_string()
+    }
 }
+
 impl AstRule for OldStyleArrayLiteral {
     fn check(_settings: &Settings, node: &Node, src: &SourceFile) -> Option<Vec<Diagnostic>> {
-        if node.to_text(src.source_text())?.starts_with("(/") {
-            return some_vec!(Diagnostic::from_node(Self {}, node));
+        let open_bracket = node.child(0)?;
+
+        if open_bracket.to_text(src.source_text())?.starts_with("(/") {
+            let close_bracket = node.children(&mut node.walk()).last()?;
+
+            let edit_open = open_bracket.edit_replacement("[".to_string());
+            let edit_close = close_bracket.edit_replacement("]".to_string());
+            let fix = Fix::safe_edits(edit_open, [edit_close]);
+
+            return some_vec!(Diagnostic::from_node(Self {}, node).with_fix(fix));
         }
         None
     }
diff --git a/fortitude/src/rules/style/relational_operators.rs b/fortitude/src/rules/style/relational_operators.rs
index bb47067..51e2044 100644
--- a/fortitude/src/rules/style/relational_operators.rs
+++ b/fortitude/src/rules/style/relational_operators.rs
@@ -1,7 +1,7 @@
 use crate::ast::FortitudeNode;
 use crate::settings::Settings;
 use crate::{AstRule, FromAstNode};
-use ruff_diagnostics::{Diagnostic, Violation};
+use ruff_diagnostics::{Diagnostic, Fix, Violation};
 use ruff_macros::{derive_message_formats, violation};
 use ruff_source_file::SourceFile;
 use tree_sitter::Node;
@@ -46,10 +46,10 @@ impl AstRule for DeprecatedRelationalOperator {
             .to_lowercase()
             .to_string();
         let new_symbol = map_relational_symbols(symbol.as_str())?.to_string();
-        some_vec![Diagnostic::from_node(
-            Self { symbol, new_symbol },
-            &relation
-        )]
+
+        let fix = Fix::safe_edit(relation.edit_replacement(new_symbol.clone()));
+
+        some_vec![Diagnostic::from_node(Self { symbol, new_symbol }, &relation).with_fix(fix)]
     }
 
     fn entrypoints() -> Vec<&'static str> {


Test patch:
diff --git a/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__deprecated-relational-operator_S051.f90.snap b/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__deprecated-relational-operator_S051.f90.snap
index 8a57f1f..8cddcfe 100644
--- a/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__deprecated-relational-operator_S051.f90.snap
+++ b/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__deprecated-relational-operator_S051.f90.snap
@@ -3,7 +3,7 @@ source: fortitude/src/rules/style/mod.rs
 expression: diagnostics
 snapshot_kind: text
 ---
-./resources/test/fixtures/style/S051.f90:2:9: S051 deprecated relational operator '.gt.', prefer '>' instead
+./resources/test/fixtures/style/S051.f90:2:9: S051 [*] deprecated relational operator '.gt.', prefer '>' instead
   |
 1 | program test
 2 |   if (0 .gt. 1) error stop
@@ -12,7 +12,15 @@ snapshot_kind: text
 4 |   if (a.eq.b.and.a.ne.b) error stop
   |
 
-./resources/test/fixtures/style/S051.f90:3:9: S051 deprecated relational operator '.le.', prefer '<=' instead
+ℹ Safe fix
+1 1 | program test
+2   |-  if (0 .gt. 1) error stop
+  2 |+  if (0 > 1) error stop
+3 3 |   if (1 .le. 0) error stop
+4 4 |   if (a.eq.b.and.a.ne.b) error stop
+5 5 |   if (1 == 2) error stop  ! OK
+
+./resources/test/fixtures/style/S051.f90:3:9: S051 [*] deprecated relational operator '.le.', prefer '<=' instead
   |
 1 | program test
 2 |   if (0 .gt. 1) error stop
@@ -22,7 +30,16 @@ snapshot_kind: text
 5 |   if (1 == 2) error stop  ! OK
   |
 
-./resources/test/fixtures/style/S051.f90:4:8: S051 deprecated relational operator '.eq.', prefer '==' instead
+ℹ Safe fix
+1 1 | program test
+2 2 |   if (0 .gt. 1) error stop
+3   |-  if (1 .le. 0) error stop
+  3 |+  if (1 <= 0) error stop
+4 4 |   if (a.eq.b.and.a.ne.b) error stop
+5 5 |   if (1 == 2) error stop  ! OK
+6 6 |   if (2 /= 2) error stop  ! OK
+
+./resources/test/fixtures/style/S051.f90:4:8: S051 [*] deprecated relational operator '.eq.', prefer '==' instead
   |
 2 |   if (0 .gt. 1) error stop
 3 |   if (1 .le. 0) error stop
@@ -32,7 +49,17 @@ snapshot_kind: text
 6 |   if (2 /= 2) error stop  ! OK
   |
 
-./resources/test/fixtures/style/S051.f90:4:19: S051 deprecated relational operator '.ne.', prefer '/=' instead
+ℹ Safe fix
+1 1 | program test
+2 2 |   if (0 .gt. 1) error stop
+3 3 |   if (1 .le. 0) error stop
+4   |-  if (a.eq.b.and.a.ne.b) error stop
+  4 |+  if (a==b.and.a.ne.b) error stop
+5 5 |   if (1 == 2) error stop  ! OK
+6 6 |   if (2 /= 2) error stop  ! OK
+7 7 | end program test
+
+./resources/test/fixtures/style/S051.f90:4:19: S051 [*] deprecated relational operator '.ne.', prefer '/=' instead
   |
 2 |   if (0 .gt. 1) error stop
 3 |   if (1 .le. 0) error stop
@@ -41,3 +68,13 @@ snapshot_kind: text
 5 |   if (1 == 2) error stop  ! OK
 6 |   if (2 /= 2) error stop  ! OK
   |
+
+ℹ Safe fix
+1 1 | program test
+2 2 |   if (0 .gt. 1) error stop
+3 3 |   if (1 .le. 0) error stop
+4   |-  if (a.eq.b.and.a.ne.b) error stop
+  4 |+  if (a.eq.b.and.a/=b) error stop
+5 5 |   if (1 == 2) error stop  ! OK
+6 6 |   if (2 /= 2) error stop  ! OK
+7 7 | end program test
diff --git a/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__old-style-array-literal_S041.f90.snap b/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__old-style-array-literal_S041.f90.snap
index 006f1f9..cdb14ee 100644
--- a/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__old-style-array-literal_S041.f90.snap
+++ b/fortitude/src/rules/style/snapshots/fortitude__rules__style__tests__old-style-array-literal_S041.f90.snap
@@ -3,7 +3,7 @@ source: fortitude/src/rules/style/mod.rs
 expression: diagnostics
 snapshot_kind: text
 ---
-./resources/test/fixtures/style/S041.f90:2:21: S041 Array literal uses old-style syntax: prefer `[...]`
+./resources/test/fixtures/style/S041.f90:2:21: S041 [*] Array literal uses old-style syntax: prefer `[...]`
   |
 1 | program test
 2 |   integer :: a(3) = (/1, 2, 3/)
@@ -11,8 +11,17 @@ snapshot_kind: text
 3 |   integer :: b(3) = (/ &
 4 |        1, &
   |
+  = help: Change `(/.../)` to `[...]`
 
-./resources/test/fixtures/style/S041.f90:3:21: S041 Array literal uses old-style syntax: prefer `[...]`
+ℹ Safe fix
+1 1 | program test
+2   |-  integer :: a(3) = (/1, 2, 3/)
+  2 |+  integer :: a(3) = [1, 2, 3]
+3 3 |   integer :: b(3) = (/ &
+4 4 |        1, &
+5 5 |        2, &
+
+./resources/test/fixtures/style/S041.f90:3:21: S041 [*] Array literal uses old-style syntax: prefer `[...]`
   |
 1 |   program test
 2 |     integer :: a(3) = (/1, 2, 3/)
@@ -26,8 +35,23 @@ snapshot_kind: text
 8 |     if (.true.) a = (/4, 5, 6/)
 9 |     b(1:3) = (/ &
   |
+  = help: Change `(/.../)` to `[...]`
+
+ℹ Safe fix
+1 1 | program test
+2 2 |   integer :: a(3) = (/1, 2, 3/)
+3   |-  integer :: b(3) = (/ &
+  3 |+  integer :: b(3) = [ &
+4 4 |        1, &
+5 5 |        2, &
+6 6 |        3 &
+7   |-       /)
+  7 |+       ]
+8 8 |   if (.true.) a = (/4, 5, 6/)
+9 9 |   b(1:3) = (/ &
+10 10 |        4, &
 
-./resources/test/fixtures/style/S041.f90:8:19: S041 Array literal uses old-style syntax: prefer `[...]`
+./resources/test/fixtures/style/S041.f90:8:19: S041 [*] Array literal uses old-style syntax: prefer `[...]`
    |
  6 |        3 &
  7 |        /)
@@ -36,8 +60,19 @@ snapshot_kind: text
  9 |   b(1:3) = (/ &
 10 |        4, &
    |
+   = help: Change `(/.../)` to `[...]`
 
-./resources/test/fixtures/style/S041.f90:9:12: S041 Array literal uses old-style syntax: prefer `[...]`
+ℹ Safe fix
+5 5 |        2, &
+6 6 |        3 &
+7 7 |        /)
+8   |-  if (.true.) a = (/4, 5, 6/)
+  8 |+  if (.true.) a = [4, 5, 6]
+9 9 |   b(1:3) = (/ &
+10 10 |        4, &
+11 11 |        5, &
+
+./resources/test/fixtures/style/S041.f90:9:12: S041 [*] Array literal uses old-style syntax: prefer `[...]`
    |
  7 |          /)
  8 |     if (.true.) a = (/4, 5, 6/)
@@ -50,3 +85,17 @@ snapshot_kind: text
    | |_________^ S041
 14 |   end program test
    |
+   = help: Change `(/.../)` to `[...]`
+
+ℹ Safe fix
+6  6  |        3 &
+7  7  |        /)
+8  8  |   if (.true.) a = (/4, 5, 6/)
+9     |-  b(1:3) = (/ &
+   9  |+  b(1:3) = [ &
+10 10 |        4, &
+11 11 |        5, &
+12 12 |        6 &
+13    |-       /)
+   13 |+       ]
+14 14 | end program test