Repository: uber/piranha (language: python)

Issue:
Output summary at the end of execution
When Piranha is run, output the summary of the run that includes the configuration under which it ran, the number of APIs considered for refactoring, the number of APIs refactored, etc. 
Related - issue #207 


Discussion / hints:
hii..I am trying to integrate piranha using maven.Also I have configured all the required jar files inside pom.xml file.Also the maven test result is getting successful.But where can I get the expected output??
I m not able to analyse where the output is generated.Please help me with this
When you build it with the given options in the instruction, the refactoring should happen in place and whatever you provided as source must have been updated. 

Thanks for quick response...actually the source is not getting updated. I am running by project->Run As->maven test.Also tried with pom.xml->Run As->maven test.Is this the correct approach? 
can you please guide me , which one to run?
Almost reached the solution referring the previous solutions to the issue raised
Glad it’s almost working! Two tips for the future:

1. If you’re having a problem with Piranha, please either open a new issue or comment on an issue reflecting the same problem. This issue is about something different.

2. If you’re having a build system integration problem, please provide output from running on the command line. Unfortunately we don’t have time to support IDE integrations at the moment.
yes sure...thanks for info :)
It would also be helpful to output "near misses" during execution.
Makes it easier to identify if the returnType or receiverType is slightly off, for instance. Or if the properties file path was specified incorrectly.

Does it make sense to log these in an optionally specified file or would that be too much overhead? 
Great idea. We can do two things here: 
a) By default, we will print the summary of what happened as part of the refactoring. 
b) Optionally, if the user specifies an option,  we can log the activity of the refactoring process at the appropriate granularity.

The latter will be more expensive but will only be used for debugging the process of refactoring. 
Could this also work independently from the in-place removals?

That is, run Piranha and only get this summary, and don't modify any files.
Yes. Polyglot Piranha allows one to express match-only rules (That change is under PR now) . So we can get this summary without replacements too. 
❤️. Can we expect this sometime this year?
Oh yea ! My plan is to do it much much sooner.  This is also an internal requirement so ...

Fix patch:
diff --git a/polyglot/README.md b/polyglot/README.md
index 29c2798b7..0bf0f695c 100644
--- a/polyglot/README.md
+++ b/polyglot/README.md
@@ -22,15 +22,17 @@ OPTIONS:
 
     -f, --path-to-configurations <PATH_TO_CONFIGURATIONS>
             Directory containing the configuration files - `piranha_arguments.toml`, `rules.toml`,
-            and `edges.toml` (optional)
+            and  `edges.toml` (optional)
 
     -h, --help
             Print help information
 
-    -V, --version
-            Print version information
+    -j, --path-to-output-summary <PATH_TO_OUTPUT_SUMMARY>
+            Path to output summary json
 ```
 
+The output JSON format is - `models.PiranhaOutputSummary`.
+
 Languages supported :
 * Java
 * Kotlin (planned)
diff --git a/polyglot/piranha/Cargo.toml b/polyglot/piranha/Cargo.toml
index 28c0d1aae..2eb45c350 100644
--- a/polyglot/piranha/Cargo.toml
+++ b/polyglot/piranha/Cargo.toml
@@ -26,6 +26,7 @@ clap = { version = "3.1.12", features = ["derive"] }
 log = "0.4.16"
 env_logger = "0.9.0"
 tempdir = "0.3"
+serde_json = "1.0.82"
 tree-sitter-kotlin = { git = "https://github.com/ketkarameya/tree-sitter-kotlin.git" }
 tree-sitter-java = { git = "https://github.com/tree-sitter/tree-sitter-java.git" }
 tree-sitter-swift = { git = "https://github.com/ketkarameya/tree-sitter-swift.git", branch = "add_parser" }
diff --git a/polyglot/piranha/demo/java/configurations/rules.toml b/polyglot/piranha/demo/java/configurations/rules.toml
index 8d7857e1a..6db4ef2e2 100755
--- a/polyglot/piranha/demo/java/configurations/rules.toml
+++ b/polyglot/piranha/demo/java/configurations/rules.toml
@@ -89,4 +89,9 @@ query = """
 replace_node = "ec"
 replace = ""
 holes = ["stale_flag_name"]
-groups = [ "delete_enum_entry"]
\ No newline at end of file
+groups = [ "delete_enum_entry"]
+
+
+[[rules]]
+name = "find_method_declarations"
+query = "((method_declaration) @method_decl)"
\ No newline at end of file
diff --git a/polyglot/piranha/demo/run_piranha_demo.sh b/polyglot/piranha/demo/run_piranha_demo.sh
index 10b97325d..97b3c2965 100755
--- a/polyglot/piranha/demo/run_piranha_demo.sh
+++ b/polyglot/piranha/demo/run_piranha_demo.sh
@@ -11,7 +11,7 @@
 
 cargo build --release
 mv target/release/piranha  demo/piranha
-./demo/piranha -c demo/java/ -f demo/java/configurations
-./demo/piranha -c demo/kt/ -f demo/kt/configurations
-./demo/piranha -c demo/swift/ -f demo/swift/configurations
-./demo/piranha -c demo/strings/ -f demo/strings/configurations
+./demo/piranha -c demo/java/ -f demo/java/configurations -j demo/java/output.json
+./demo/piranha -c demo/kt/ -f demo/kt/configurations -j demo/kt/output.json
+./demo/piranha -c demo/swift/ -f demo/swift/configurations -j demo/swift/output.json
+./demo/piranha -c demo/strings/ -f demo/strings/configurations -j demo/strings/output.json
diff --git a/polyglot/piranha/src/config.rs b/polyglot/piranha/src/config.rs
index 87e1ca99c..0eab10476 100644
--- a/polyglot/piranha/src/config.rs
+++ b/polyglot/piranha/src/config.rs
@@ -33,12 +33,15 @@ use clap::Parser;
 #[derive(Clone, Parser, Debug)]
 #[clap(name = "Piranha")]
 pub(crate) struct CommandLineArguments {
-  /// Path to source code folder.
+  /// Path to source code folder
   #[clap(short = 'c', long)]
   pub(crate) path_to_codebase: String,
-  /// Directory containing the configuration files - `piranha_arguments.toml`, `rules.toml`,  and  `edges.toml` (optional).
+  /// Directory containing the configuration files - `piranha_arguments.toml`, `rules.toml`,  and  `edges.toml` (optional)
   #[clap(short = 'f', long)]
   pub(crate) path_to_configurations: String,
+  /// Path to output summary json
+  #[clap(short = 'j', long)]
+  pub(crate) path_to_output_summary: Option<String>,
 }
 
 pub(crate) fn read_config_files(
diff --git a/polyglot/piranha/src/main.rs b/polyglot/piranha/src/main.rs
index dcb8761f7..ac359b9a7 100644
--- a/polyglot/piranha/src/main.rs
+++ b/polyglot/piranha/src/main.rs
@@ -12,7 +12,7 @@ Copyright (c) 2022 Uber Technologies, Inc.
 */
 
 //! Defines the entry-point for Piranha.
-use std::time::Instant;
+use std::{fs, time::Instant};
 
 use crate::{
   models::piranha_arguments::PiranhaArguments, piranha::execute_piranha,
@@ -21,6 +21,7 @@ use crate::{
 use clap::StructOpt;
 use config::CommandLineArguments;
 use log::info;
+use models::piranha_output::PiranhaOutputSummary;
 
 mod config;
 mod models;
@@ -35,11 +36,33 @@ fn main() {
 
   let args = PiranhaArguments::new(CommandLineArguments::parse());
 
-  let (updated_files, _matches, _rewrites) = execute_piranha(&args);
+  let (source_code_units, piranha_output_summaries) = execute_piranha(&args);
 
-  for source_code_unit in updated_files {
-    source_code_unit.persist(&args);
+  for scu in source_code_units {
+    scu.persist(&args);
+  }
+
+  if args.path_to_output_summaries().is_some() {
+    write_output_summary(
+      piranha_output_summaries,
+      args.path_to_output_summaries().unwrap(),
+    );
   }
 
   info!("Time elapsed - {:?}", now.elapsed().as_secs());
 }
+
+/// Writes the output summaries to a Json file named `path_to_output_summaries` .
+fn write_output_summary(
+  piranha_output_summaries: Vec<PiranhaOutputSummary>, path_to_json: &String,
+) {
+  if let Ok(contents) = serde_json::to_string_pretty(&piranha_output_summaries) {
+    if let Ok(_) = fs::write(path_to_json, contents) {
+      return;
+    }
+  }
+  panic!(
+    "Could not write the output summary to the file - {}",
+    path_to_json
+  );
+}
diff --git a/polyglot/piranha/src/models/edit.rs b/polyglot/piranha/src/models/edit.rs
index 18a692ded..ff2269c82 100644
--- a/polyglot/piranha/src/models/edit.rs
+++ b/polyglot/piranha/src/models/edit.rs
@@ -1,23 +1,24 @@
 use std::collections::HashMap;
 
+use serde_derive::{Serialize};
 use tree_sitter::Range;
 
-use super::{rule::Rule, matches::Match};
+use super::{matches::Match};
 
-#[derive(Debug, Clone)]
+#[derive(Serialize, Debug, Clone)]
 pub(crate) struct Edit {
   // The match representing the target site of the edit
   p_match: Match,
   // The string to replace the substring encompassed by the match
   replacement_string: String,
   // The rule used for creating this match-replace
-  matched_rule: Rule,
+  matched_rule: String,
   
 }
 
 impl Edit {
   pub(crate) fn new(
-    p_match: Match, replacement_string: String, matched_rule: Rule,
+    p_match: Match, replacement_string: String, matched_rule: String,
   ) -> Self {
     Self {
       p_match,
@@ -31,7 +32,7 @@ impl Edit {
     Self::new(
       Match::new(replacement_range, HashMap::new()),
       replacement_string,
-      Rule::dummy(),
+      String::new(),
     )
   }
 
@@ -44,7 +45,7 @@ impl Edit {
     self.replacement_string.as_ref()
   }
 
-  pub(crate) fn matched_rule(&self) -> Rule {
+  pub(crate) fn matched_rule(&self) -> String {
     self.matched_rule.clone()
   }
 
diff --git a/polyglot/piranha/src/models/matches.rs b/polyglot/piranha/src/models/matches.rs
index f6d7c5c39..c04ab8958 100644
--- a/polyglot/piranha/src/models/matches.rs
+++ b/polyglot/piranha/src/models/matches.rs
@@ -13,24 +13,21 @@ Copyright (c) 2022 Uber Technologies, Inc.
 
 use std::collections::HashMap;
 
-use tree_sitter::{Range};
+use serde::{Serialize, Serializer};
+use tree_sitter::Range;
 
-#[derive(Debug, Clone)]
+#[derive(serde_derive::Serialize, Debug, Clone)]
 pub(crate) struct Match {
   // Range of the entire AST node captured by the match
+  #[serde(serialize_with = "ser_range")]
   range: Range,
   // The mapping between tags and string representation of the AST captured.
   matches: HashMap<String, String>,
 }
 
 impl Match {
-  pub(crate) fn new(
-    range: Range, matches: HashMap<String, String>,
-  ) -> Self {
-    Self {
-      range,
-      matches,
-    }
+  pub(crate) fn new(range: Range, matches: HashMap<String, String>) -> Self {
+    Self { range, matches }
   }
 
   /// Get the edit's replacement range.
@@ -41,5 +38,44 @@ impl Match {
   pub(crate) fn matches(&self) -> &HashMap<String, String> {
     &self.matches
   }
+}
+
+/// This method serializes the `tree_sitter::Range` struct. Originally, it does not derive serde::Serialize
+/// So in this method, we cast `Range` to a local type `LocalRange` and serialize this casted object. 
+/// Note `LocalRange` derives serialize.
+fn ser_range<S: Serializer>(range: &Range, serializer: S) -> Result<S::Ok, S::Error> {
+  let local_range = LocalRange {
+    start_byte: range.start_byte,
+    end_byte: range.end_byte,
+    start_point: LocalPoint {
+      row: range.start_point.row,
+      column: range.start_point.column,
+    },
+    end_point: LocalPoint {
+      row: range.end_point.row,
+      column: range.end_point.column,
+    },
+  };
+
+  // Instead of serializing Vec<ExternalCrateColor>, we serialize Vec<LocalColor>.
+  local_range.serialize(serializer)
+}
+
+/// A range of positions in a multi-line text document, both in terms of bytes and of
+/// rows and columns.
+/// Note `LocalRange` derives serialize.
+#[derive(serde_derive::Serialize, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+struct LocalRange {
+  start_byte: usize,
+  end_byte: usize,
+  start_point: LocalPoint,
+  end_point: LocalPoint,
+}
 
+/// A range of positions in a multi-line text document, both in terms of bytes and of
+/// rows and columns.
+#[derive(serde_derive::Serialize, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+struct LocalPoint {
+  row: usize,
+  column: usize,
 }
diff --git a/polyglot/piranha/src/models/mod.rs b/polyglot/piranha/src/models/mod.rs
index d38e7ff59..803210ac2 100644
--- a/polyglot/piranha/src/models/mod.rs
+++ b/polyglot/piranha/src/models/mod.rs
@@ -22,3 +22,4 @@ pub(crate) mod rule_store;
 pub(crate) mod scopes;
 pub(crate) mod source_code_unit;
 pub(crate) mod matches;
+pub(crate) mod piranha_output;
diff --git a/polyglot/piranha/src/models/piranha_arguments.rs b/polyglot/piranha/src/models/piranha_arguments.rs
index 775863bfc..1c2fdf746 100644
--- a/polyglot/piranha/src/models/piranha_arguments.rs
+++ b/polyglot/piranha/src/models/piranha_arguments.rs
@@ -34,6 +34,8 @@ pub(crate) struct PiranhaArguments {
   input_substitutions: HashMap<String, String>,
   /// Folder containing the API specific rules
   path_to_configurations: String,
+  /// File to which the output summary should be written 
+  path_to_output_summaries : Option<String>,
   /// Tree-sitter language model
   language: Language,
   // The language name is file the extension used for files in particular language.
@@ -62,6 +64,7 @@ impl PiranhaArguments {
       path_to_code_base: args.path_to_codebase.to_string(),
       input_substitutions,
       path_to_configurations: args.path_to_configurations,
+      path_to_output_summaries: args.path_to_output_summary,
       language_name: String::from(&piranha_args_from_config.language()),
       language: piranha_args_from_config.language().get_language(),
       delete_file_if_empty: piranha_args_from_config
@@ -69,7 +72,7 @@ impl PiranhaArguments {
         .unwrap_or(true),
       delete_consecutive_new_lines: piranha_args_from_config
         .delete_consecutive_new_lines()
-        .unwrap_or(true),
+        .unwrap_or(false),
     }
   }
 
@@ -100,6 +103,10 @@ impl PiranhaArguments {
   pub(crate) fn delete_consecutive_new_lines(&self) -> bool {
     self.delete_consecutive_new_lines
   }
+
+    pub(crate) fn path_to_output_summaries(&self) -> Option<&String> {
+        self.path_to_output_summaries.as_ref()
+    }
 }
 
 #[cfg(test)]
@@ -118,6 +125,7 @@ impl PiranhaArguments {
       path_to_code_base: String::new(),
       input_substitutions: HashMap::new(),
       path_to_configurations: String::new(),
+      path_to_output_summaries: None,
       language: language_name.get_language(),
       language_name,
       delete_consecutive_new_lines: false,
@@ -131,6 +139,7 @@ impl PiranhaArguments {
       path_to_code_base: String::new(),
       input_substitutions: HashMap::new(),
       path_to_configurations: String::new(),
+      path_to_output_summaries: None,
       language: language_name.get_language(),
       language_name,
       delete_consecutive_new_lines: false,
diff --git a/polyglot/piranha/src/models/piranha_output.rs b/polyglot/piranha/src/models/piranha_output.rs
new file mode 100644
index 000000000..c45bad878
--- /dev/null
+++ b/polyglot/piranha/src/models/piranha_output.rs
@@ -0,0 +1,39 @@
+use itertools::Itertools;
+use serde_derive::Serialize;
+
+use super::{edit::Edit, matches::Match, source_code_unit::SourceCodeUnit};
+
+#[derive(Serialize, Debug, Clone, Default)]
+pub(crate) struct PiranhaOutputSummary {
+  path: String,
+  content: String,
+  matches: Vec<(String, Match)>,
+  rewrites: Vec<Edit>,
+}
+
+impl PiranhaOutputSummary {
+  pub(crate) fn new(source_code_unit: &SourceCodeUnit) -> PiranhaOutputSummary {
+    return PiranhaOutputSummary {
+      path: String::from(
+        source_code_unit
+          .path()
+          .as_os_str()
+          .to_str()
+          .unwrap(),
+      ),
+      content: source_code_unit.code(),
+      matches: source_code_unit.matches().iter().cloned().collect_vec(),
+      rewrites: source_code_unit.rewrites().iter().cloned().collect_vec(),
+    };
+  }
+  
+  #[cfg(test)]
+  pub(crate) fn matches(&self) -> &[(String, Match)] {
+    self.matches.as_ref()
+  }
+
+  #[cfg(test)]
+  pub(crate) fn rewrites(&self) -> &[Edit] {
+    self.rewrites.as_ref()
+  }
+}
diff --git a/polyglot/piranha/src/models/rule.rs b/polyglot/piranha/src/models/rule.rs
index 4758be922..413786dad 100644
--- a/polyglot/piranha/src/models/rule.rs
+++ b/polyglot/piranha/src/models/rule.rs
@@ -287,7 +287,7 @@ impl Rule {
         return Edit::new(
           p_match.clone(),
           replacement,
-          self.clone()
+          self.name()
         );
       });
   }
@@ -318,10 +318,6 @@ impl Rule {
       grep_heuristics: None,
     }
   }
-
-  pub(crate) fn dummy() -> Rule {
-    Rule::new("", "", "", "", None, None)
-  }
 }
 
 #[cfg(test)]
diff --git a/polyglot/piranha/src/models/rule_store.rs b/polyglot/piranha/src/models/rule_store.rs
index 18f782414..aa5e48cc4 100644
--- a/polyglot/piranha/src/models/rule_store.rs
+++ b/polyglot/piranha/src/models/rule_store.rs
@@ -113,9 +113,9 @@ impl RuleStore {
 
   /// Get the next rules to be applied grouped by the scope in which they should be performed.
   pub(crate) fn get_next(
-    &self, rule: &Rule, tag_matches: &HashMap<String, String>,
+    &self, rule_name: &String, tag_matches: &HashMap<String, String>,
   ) -> HashMap<String, Vec<Rule>> {
-    let rule_name = rule.name();
+    // let rule_name = rule.name();
     let mut next_rules: HashMap<String, Vec<Rule>> = HashMap::new();
     // Iterate over each entry (Edge) in the adjacency list corresponding to `rule_name`
     for (scope, to_rule) in self.rule_graph.get_neighbors(&rule_name) {
@@ -123,7 +123,7 @@ impl RuleStore {
       // If the to_rule_name is a dummy rule, skip it and rather return it's next rules.
       if to_rule_name.is_dummy_rule() {
         // Call this method recursively on the dummy node
-        for (next_next_rules_scope, next_next_rules) in self.get_next(to_rule_name, tag_matches) {
+        for (next_next_rules_scope, next_next_rules) in self.get_next(&to_rule_name.name(), tag_matches) {
           for next_next_rule in next_next_rules {
             // Group the next rules based on the scope
             next_rules.collect(
diff --git a/polyglot/piranha/src/models/source_code_unit.rs b/polyglot/piranha/src/models/source_code_unit.rs
index 8b24f16df..b7d63c7db 100644
--- a/polyglot/piranha/src/models/source_code_unit.rs
+++ b/polyglot/piranha/src/models/source_code_unit.rs
@@ -10,7 +10,7 @@ use tree_sitter_traversal::{traverse, Order};
 
 use crate::utilities::{eq_without_whitespace, tree_sitter_utilities::get_tree_sitter_edit};
 
-use super::{edit::Edit, piranha_arguments::PiranhaArguments, matches::Match, rule::Rule};
+use super::{edit::Edit, piranha_arguments::PiranhaArguments, matches::Match};
 
 // Maintains the updated source code content and AST of the file
 #[derive(Clone)]
@@ -27,7 +27,7 @@ pub struct SourceCodeUnit {
   // Rewrites applied to this source code unit
   rewrites: Vec<Edit>,
   // Matches for the read_only rules in this source code unit
-  matches: Vec<(Rule, Match)>
+  matches: Vec<(String, Match)>
 
 
 }
@@ -202,15 +202,19 @@ impl SourceCodeUnit {
         self.rewrites.as_ref()
     }
 
+    pub(crate) fn path(&self) -> &PathBuf {
+      &self.path
+    }
+
     pub(crate) fn rewrites_mut(&mut self) -> &mut Vec<Edit> {
         &mut self.rewrites
     }
 
-    pub(crate) fn matches(&self) -> &[(Rule, Match)] {
+    pub(crate) fn matches(&self) -> &[(String, Match)] {
         self.matches.as_ref()
     }
 
-    pub(crate) fn matches_mut(&mut self) -> &mut Vec<(Rule, Match)> {
+    pub(crate) fn matches_mut(&mut self) -> &mut Vec<(String, Match)> {
         &mut self.matches
     }
 }
diff --git a/polyglot/piranha/src/piranha.rs b/polyglot/piranha/src/piranha.rs
index 9c39710a7..25d76aab4 100644
--- a/polyglot/piranha/src/piranha.rs
+++ b/polyglot/piranha/src/piranha.rs
@@ -21,9 +21,8 @@ use regex::Regex;
 use tree_sitter::{Parser, Range};
 
 use crate::{
-  models::{
-    edit::Edit, matches::Match, piranha_arguments::PiranhaArguments, rule_store::RuleStore,
-    source_code_unit::SourceCodeUnit,
+  models::{piranha_arguments::PiranhaArguments,
+    piranha_output::PiranhaOutputSummary, rule_store::RuleStore, source_code_unit::SourceCodeUnit,
   },
   utilities::{read_file, tree_sitter_utilities::get_match_and_replace_range},
 };
@@ -39,24 +38,15 @@ use crate::{
 };
 
 use std::collections::VecDeque;
-use tree_sitter::{Node};
+use tree_sitter::Node;
 
-/// Executes piranha for the given configuration 
+/// Executes piranha for the given configuration
 /// Returns (List of updated piranha files, Map of matches found for each file, map of rewrites performed in each file)
-pub(crate) fn execute_piranha(
-  configuration: &PiranhaArguments,
-) -> (
-  Vec<SourceCodeUnit>,
-  HashMap<PathBuf, Vec<(Rule, Match)>>,
-  HashMap<PathBuf, Vec<Edit>>,
-) {
+pub(crate) fn execute_piranha(configuration: &PiranhaArguments) -> (Vec<SourceCodeUnit>, Vec<PiranhaOutputSummary>) {
   let mut flag_cleaner = FlagCleaner::new(configuration);
   flag_cleaner.perform_cleanup();
-  (
-    flag_cleaner.get_updated_files(),
-    flag_cleaner.get_all_matches(),
-    flag_cleaner.get_all_rewrites(),
-  )
+  // flag_cleaner.relevant_files
+  (flag_cleaner.get_updated_files(), flag_cleaner.get_updated_files().iter().map(|f|PiranhaOutputSummary::new(f)).collect_vec())
 }
 
 impl SourceCodeUnit {
@@ -102,7 +92,7 @@ impl SourceCodeUnit {
     // When rule is a "rewrite" rule :
     // Update the first match of the rewrite rule
     // Add mappings to the substitution
-    // Propagate each applied edit. The next rule will be applied relative to the application of this edit. 
+    // Propagate each applied edit. The next rule will be applied relative to the application of this edit.
     if !rule.is_match_only_rule() {
       if let Some(edit) = rule.get_edit(&self.clone(), rule_store, scope_node, true) {
         self.rewrites_mut().push(edit.clone());
@@ -114,24 +104,29 @@ impl SourceCodeUnit {
         // Apply edit_1
         let applied_ts_edit = self.apply_edit(&edit, parser);
 
-        self.propagate(get_match_and_replace_range(applied_ts_edit), rule, rule_store, parser);
+        self.propagate(
+          get_match_and_replace_range(applied_ts_edit),
+          rule,
+          rule_store,
+          parser,
+        );
       }
     }
     // When rule is a "match-only" rule :
     // Get all the matches
     // Add mappings to the substitution
-    // Propagate each match. Note that,  we pass a identity edit (where old range == new range) in to the propagate logic. 
-    // The next edit will be applied relative to the identity edit. 
+    // Propagate each match. Note that,  we pass a identity edit (where old range == new range) in to the propagate logic.
+    // The next edit will be applied relative to the identity edit.
     else {
       for m in rule.get_matches(&self.clone(), rule_store, scope_node, true) {
-        self.matches_mut().push((rule.clone(), m.clone()));
-                
+        self.matches_mut().push((rule.name(), m.clone()));
+
         // In this scenario we pass the match and replace range as the range of the match `m`
         // This is equivalent to propagating an identity rule
         //  i.e. a rule that replaces the matched code with itself
         // Note that, here we DO NOT invoke the `_apply_edit` method and only update the `substitutions`
-        // But NOT invoking this we simulate the application of an identity rule 
-        // 
+        // By NOT invoking this we simulate the application of an identity rule
+        //
         self.add_to_substitutions(m.matches());
 
         self.propagate((m.range(), m.range()), rule.clone(), rule_store, parser);
@@ -157,12 +152,12 @@ impl SourceCodeUnit {
   ///  (iv) Apply the rules based on custom language specific scopes (as defined in `<language>/scope_config.toml`) (recursive)
   ///
   fn propagate(
-    &mut self, (match_range, replace_range): (Range, Range), rule: Rule, rules_store: &mut RuleStore,
-    parser: &mut Parser,
+    &mut self, (match_range, replace_range): (Range, Range), rule: Rule,
+    rules_store: &mut RuleStore, parser: &mut Parser,
   ) {
     let (mut current_match_range, mut current_replace_range) = (match_range, replace_range);
 
-    let mut current_rule = rule.clone();
+    let mut current_rule = rule.name();
     let mut next_rules_stack: VecDeque<(String, Rule)> = VecDeque::new();
     // Perform the parent edits, while queueing the Method and Class level edits.
     // let file_level_scope_names = [METHOD, CLASS];
@@ -193,9 +188,16 @@ impl SourceCodeUnit {
         &next_rules_by_scope[PARENT],
       ) {
         self.rewrites_mut().push(edit.clone());
-        info!( "{}", format!( "Cleaning up the context, by applying the rule - {}", edit.matched_rule().name()).green());
+        info!(
+          "{}",
+          format!(
+            "Cleaning up the context, by applying the rule - {}",
+            edit.matched_rule()
+          )
+          .green()
+        );
         // Apply the matched rule to the parent
-        let applied_edit = self.apply_edit(&edit, parser); 
+        let applied_edit = self.apply_edit(&edit, parser);
         (current_match_range, current_replace_range) = get_match_and_replace_range(applied_edit);
         current_rule = edit.matched_rule();
         // Add the (tag, code_snippet) mapping to substitution table.
@@ -279,29 +281,12 @@ struct FlagCleaner {
 
 impl FlagCleaner {
   fn get_updated_files(&self) -> Vec<SourceCodeUnit> {
-    self.relevant_files.values().cloned().collect_vec()
-  }
-
-  ///  Reports all the matches found by Piranha grouped by the file path.
-  ///  Returns a map where key is the file path, and value is a list of matches found for a rule(s) in this file.
-  fn get_all_matches(&self) -> HashMap<PathBuf, Vec<(Rule, Match)>> {
     self
       .relevant_files
-      .iter()
-      .map(|(k, v)| (k.clone(), v.matches().iter().cloned().collect_vec()))
-      .filter(|(_, v)| !v.is_empty())
-      .collect()
-  }
-
-  ///  Reports all the matches found by Piranha grouped by the file path.
-  ///  Returns a map where key is the file path, and value is a list of edits performed in this file.
-  fn get_all_rewrites(&self) -> HashMap<PathBuf, Vec<Edit>> {
-    self
-      .relevant_files
-      .iter()
-      .map(|(k, v)| (k.clone(), v.rewrites().iter().cloned().collect_vec()))
-      .filter(|(_, v)| !v.is_empty())
-      .collect()
+      .values()
+      .filter(|r| !r.matches().is_empty() || !r.rewrites().is_empty())
+      .cloned()
+      .collect_vec()
   }
 
   /// Performs cleanup related to stale flags


Test patch:
diff --git a/polyglot/piranha/src/models/unit_tests/source_code_unit_test.rs b/polyglot/piranha/src/models/unit_tests/source_code_unit_test.rs
index dd114f8cf..2ae94437b 100644
--- a/polyglot/piranha/src/models/unit_tests/source_code_unit_test.rs
+++ b/polyglot/piranha/src/models/unit_tests/source_code_unit_test.rs
@@ -22,11 +22,6 @@ use {
 
 impl SourceCodeUnit {
   
-  pub(crate) fn path(&self) -> &PathBuf {
-    &self.path
-  }
-
-
   pub(crate) fn dummy_unit(content: &str, parser: &mut Parser) -> Self{
     SourceCodeUnit::new(
       parser,
diff --git a/polyglot/piranha/src/tests/mod.rs b/polyglot/piranha/src/tests/mod.rs
index 235282911..458e5b336 100644
--- a/polyglot/piranha/src/tests/mod.rs
+++ b/polyglot/piranha/src/tests/mod.rs
@@ -42,10 +42,11 @@ fn run_match_test(relative_path_to_tests: &str, number_of_matches: usize) {
   let args = PiranhaArguments::new(CommandLineArguments {
     path_to_codebase: format!("{path_to_test_ff}/input/"),
     path_to_configurations: format!("{path_to_test_ff}/configurations/"),
+    path_to_output_summary: None
   });
-  let (_updated_files, matches,_rewrites) = execute_piranha(&args);
+  let (_updated_files, output_summaries) = execute_piranha(&args);
   
-  assert_eq!(matches.values().flat_map(|x|x.iter()).count(), number_of_matches);
+  assert_eq!(output_summaries.iter().flat_map(|os|os.matches().iter()).count(), number_of_matches);
 }
 
 // Runs a piranha over the target `<relative_path_to_tests>/input` (using configurations `<relative_path_to_tests>/configuration`) 
@@ -57,10 +58,11 @@ fn run_rewrite_test(relative_path_to_tests: &str, n_files_changed: usize) {
   let args = PiranhaArguments::new(CommandLineArguments {
     path_to_codebase: format!("{path_to_test_ff}/input/"),
     path_to_configurations: format!("{path_to_test_ff}/configurations/"),
+    path_to_output_summary: None
   });
-  let (updated_files, _matches, rewrites) = execute_piranha(&args);
+  let (updated_files, output_summaries) = execute_piranha(&args);
   // Checks if there are any rewrites performed for the file
-  assert!(rewrites.values().flat_map(|x|x.iter()).count() > 0);
+  assert!(output_summaries.iter().flat_map(|os|os.rewrites().iter()).count() > 0);
 
   assert_eq!(updated_files.len(), n_files_changed);
   let path_to_expected =