Repository: ast-grep/ast-grep (rust)

Issue:
refactor: remove Matches/Diffs macro in cli

Pull request:
refactor: remove Matches/Diffs macro in cli
fix #1819

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Unified the output formatting across all display modes—including colored, interactive, and JSON views—for more consistent and reliable presentation of reports.
  - Simplified how results are processed before display, ensuring clear and dependable match and difference reports.
  
- **Tests**
  - Updated testing routines to verify the improved output handling, contributing to a more stable and predictable user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Patch:
diff --git a/crates/cli/src/print/cloud_print.rs b/crates/cli/src/print/cloud_print.rs
index 80de95c9..226fe084 100644
--- a/crates/cli/src/print/cloud_print.rs
+++ b/crates/cli/src/print/cloud_print.rs
@@ -1,26 +1,15 @@
-use super::{Diff, Printer};
+use super::{Diff, NodeMatch, Printer};
 use crate::lang::SgLang;
 use ast_grep_config::{RuleConfig, Severity};
 use clap::ValueEnum;
 
 use anyhow::Result;
-use ast_grep_core::{NodeMatch as SgNodeMatch, StrDoc};
 use codespan_reporting::files::SimpleFile;
 use std::io::{Stdout, Write};
 
-type NodeMatch<'a, L> = SgNodeMatch<'a, StrDoc<L>>;
-
 use std::borrow::Cow;
 use std::path::{Path, PathBuf};
 
-// add this macro because neither trait_alias nor type_alias_impl is supported.
-macro_rules! Matches {
-  ($lt: lifetime) => { impl Iterator<Item = NodeMatch<$lt, SgLang>> };
-}
-macro_rules! Diffs {
-  ($lt: lifetime) => { impl Iterator<Item = Diff<$lt>> };
-}
-
 #[derive(PartialEq, Eq, Clone, ValueEnum)]
 #[clap(rename_all = "lower")]
 pub enum Platform {
@@ -44,9 +33,9 @@ impl CloudPrinter<Stdout> {
 }
 
 impl<W: Write> Printer for CloudPrinter<W> {
-  fn print_rule<'a>(
+  fn print_rule(
     &mut self,
-    matches: Matches!('a),
+    matches: Vec<NodeMatch>,
     file: SimpleFile<Cow<str>, &String>,
     rule: &RuleConfig<SgLang>,
   ) -> Result<()> {
@@ -54,11 +43,11 @@ impl<W: Write> Printer for CloudPrinter<W> {
     print_rule(self, matches, &path, rule)
   }
 
-  fn print_matches<'a>(&mut self, _m: Matches!('a), _p: &Path) -> Result<()> {
+  fn print_matches(&mut self, _m: Vec<NodeMatch>, _p: &Path) -> Result<()> {
     unreachable!()
   }
 
-  fn print_diffs<'a>(&mut self, _d: Diffs!('a), _p: &Path) -> Result<()> {
+  fn print_diffs(&mut self, _d: Vec<Diff>, _p: &Path) -> Result<()> {
     unreachable!()
   }
 
@@ -68,15 +57,15 @@ impl<W: Write> Printer for CloudPrinter<W> {
     path: &Path,
   ) -> Result<()> {
     for (diff, rule) in diffs {
-      print_rule(self, std::iter::once(diff.node_match), path, rule)?;
+      print_rule(self, vec![diff.node_match], path, rule)?;
     }
     Ok(())
   }
 }
 
-fn print_rule<'a, W: Write>(
+fn print_rule<W: Write>(
   p: &mut CloudPrinter<W>,
-  matches: Matches!('a),
+  matches: Vec<NodeMatch>,
   path: &Path,
   rule: &RuleConfig<SgLang>,
 ) -> Result<()> {
@@ -142,7 +131,7 @@ language: TypeScript
     let mut printer = make_test_printer();
     let grep = SgLang::from(SupportLang::Tsx).ast_grep(&src);
     let rule = make_rule(rule_str);
-    let matches = grep.root().find_all(&rule.matcher);
+    let matches = grep.root().find_all(&rule.matcher).collect();
     let file = SimpleFile::new(Cow::Borrowed("test.tsx"), &src);
     printer.print_rule(matches, file, &rule).unwrap();
     let actual = get_text(&mut printer);
diff --git a/crates/cli/src/print/colored_print.rs b/crates/cli/src/print/colored_print.rs
index 5c0810f9..c339a348 100644
--- a/crates/cli/src/print/colored_print.rs
+++ b/crates/cli/src/print/colored_print.rs
@@ -1,4 +1,4 @@
-use super::{Diff, Printer};
+use super::{Diff, NodeMatch, Printer};
 use crate::lang::SgLang;
 use ast_grep_config::{RuleConfig, Severity};
 use ast_grep_core::DisplayContext;
@@ -19,17 +19,6 @@ use std::path::Path;
 
 mod test;
 
-use ast_grep_core::{NodeMatch as SgNodeMatch, StrDoc};
-type NodeMatch<'a, L> = SgNodeMatch<'a, StrDoc<L>>;
-
-// add this macro because neither trait_alias nor type_alias_impl is supported.
-macro_rules! Matches {
-  ($lt: lifetime) => { impl Iterator<Item = NodeMatch<$lt, SgLang>> };
-}
-macro_rules! Diffs {
-  ($lt: lifetime) => { impl Iterator<Item = Diff<$lt>> };
-}
-
 #[derive(Clone, Copy, ValueEnum)]
 pub enum ReportStyle {
   /// Output a richly formatted diagnostic, with source code previews.
@@ -128,9 +117,9 @@ impl<W: WriteColor> ColoredPrinter<W> {
 }
 
 impl<W: WriteColor> Printer for ColoredPrinter<W> {
-  fn print_rule<'a>(
+  fn print_rule(
     &mut self,
-    matches: Matches!('a),
+    matches: Vec<NodeMatch>,
     file: SimpleFile<Cow<str>, &String>,
     rule: &RuleConfig<SgLang>,
   ) -> Result<()> {
@@ -162,7 +151,7 @@ impl<W: WriteColor> Printer for ColoredPrinter<W> {
     Ok(())
   }
 
-  fn print_matches<'a>(&mut self, matches: Matches!('a), path: &Path) -> Result<()> {
+  fn print_matches(&mut self, matches: Vec<NodeMatch>, path: &Path) -> Result<()> {
     if self.heading.should_print() {
       print_matches_with_heading(matches, path, self)
     } else {
@@ -170,7 +159,7 @@ impl<W: WriteColor> Printer for ColoredPrinter<W> {
     }
   }
 
-  fn print_diffs<'a>(&mut self, diffs: Diffs!('a), path: &Path) -> Result<()> {
+  fn print_diffs(&mut self, diffs: Vec<Diff>, path: &Path) -> Result<()> {
     let context = self.diff_context();
     let writer = &mut self.writer;
     print_diffs(diffs, path, &self.styles, writer, context)
@@ -211,7 +200,7 @@ impl<W: WriteColor> Printer for ColoredPrinter<W> {
 
 fn print_rule_title<W: WriteColor>(
   rule: &RuleConfig<SgLang>,
-  nm: &NodeMatch<SgLang>,
+  nm: &NodeMatch,
   style: &RuleStyle,
   writer: &mut W,
 ) -> Result<()> {
@@ -263,7 +252,7 @@ struct MatchMerger<'a> {
 }
 
 impl<'a> MatchMerger<'a> {
-  fn new(nm: &NodeMatch<'a, SgLang>, (before, after): (u16, u16)) -> Self {
+  fn new(nm: &NodeMatch<'a>, (before, after): (u16, u16)) -> Self {
     let display = nm.display_context(before as usize, after as usize);
     let last_start_line = display.start_line + 1;
     let last_end_line = nm.end_pos().line() + 1;
@@ -279,7 +268,7 @@ impl<'a> MatchMerger<'a> {
   }
 
   // merge non-overlapping matches but start/end on the same line
-  fn merge_adjacent(&mut self, nm: &NodeMatch<'a, SgLang>) -> Option<usize> {
+  fn merge_adjacent(&mut self, nm: &NodeMatch<'a>) -> Option<usize> {
     let display = self.display(nm);
     let start_line = display.start_line;
     if start_line <= self.last_end_line + self.context.1 as usize {
@@ -292,7 +281,7 @@ impl<'a> MatchMerger<'a> {
     }
   }
 
-  fn conclude_match(&mut self, nm: &NodeMatch<'a, SgLang>) {
+  fn conclude_match(&mut self, nm: &NodeMatch<'a>) {
     let display = self.display(nm);
     self.last_start_line = display.start_line + 1;
     self.last_end_line = nm.end_pos().line() + 1;
@@ -301,7 +290,7 @@ impl<'a> MatchMerger<'a> {
   }
 
   #[inline]
-  fn check_overlapping(&self, nm: &NodeMatch<'a, SgLang>) -> bool {
+  fn check_overlapping(&self, nm: &NodeMatch<'a>) -> bool {
     let range = nm.range();
 
     // merge overlapping matches.
@@ -315,17 +304,18 @@ impl<'a> MatchMerger<'a> {
     }
   }
 
-  fn display(&self, nm: &NodeMatch<'a, SgLang>) -> DisplayContext<'a> {
+  fn display(&self, nm: &NodeMatch<'a>) -> DisplayContext<'a> {
     let (before, after) = self.context;
     nm.display_context(before as usize, after as usize)
   }
 }
 
-fn print_matches_with_heading<'a, W: WriteColor>(
-  mut matches: Matches!('a),
+fn print_matches_with_heading<W: WriteColor>(
+  matches: Vec<NodeMatch>,
   path: &Path,
   printer: &mut ColoredPrinter<W>,
 ) -> Result<()> {
+  let mut matches = matches.into_iter();
   let styles = &printer.styles;
   let context_span = printer.context_span();
   let writer = &mut printer.writer;
@@ -379,11 +369,12 @@ fn print_matches_with_heading<'a, W: WriteColor>(
   Ok(())
 }
 
-fn print_matches_with_prefix<'a, W: WriteColor>(
-  mut matches: Matches!('a),
+fn print_matches_with_prefix<W: WriteColor>(
+  matches: Vec<NodeMatch>,
   path: &Path,
   printer: &mut ColoredPrinter<W>,
 ) -> Result<()> {
+  let mut matches = matches.into_iter();
   let styles = &printer.styles;
   let context_span = printer.context_span();
   let writer = &mut printer.writer;
@@ -428,13 +419,14 @@ fn print_matches_with_prefix<'a, W: WriteColor>(
   Ok(())
 }
 
-fn print_diffs<'a, W: WriteColor>(
-  mut diffs: Diffs!('a),
+fn print_diffs<W: WriteColor>(
+  diffs: Vec<Diff>,
   path: &Path,
   styles: &PrintStyles,
   writer: &mut W,
   context: usize,
 ) -> Result<()> {
+  let mut diffs = diffs.into_iter();
   print_prelude(path, styles, writer)?;
   let Some(first_diff) = diffs.next() else {
     return Ok(());
diff --git a/crates/cli/src/print/interactive_print.rs b/crates/cli/src/print/interactive_print.rs
index f04af09b..7afa189a 100644
--- a/crates/cli/src/print/interactive_print.rs
+++ b/crates/cli/src/print/interactive_print.rs
@@ -1,26 +1,15 @@
-use super::{Diff, Printer};
+use super::{Diff, NodeMatch, Printer};
 use crate::lang::SgLang;
 use crate::utils;
 use crate::utils::ErrorContext as EC;
 
 use anyhow::{Context, Result};
 use ast_grep_config::RuleConfig;
-use ast_grep_core::{NodeMatch as SgNodeMatch, StrDoc};
 use codespan_reporting::files::SimpleFile;
 
-type NodeMatch<'a, L> = SgNodeMatch<'a, StrDoc<L>>;
-
 use std::borrow::Cow;
 use std::path::{Path, PathBuf};
 
-// add this macro because neither trait_alias nor type_alias_impl is supported.
-macro_rules! Matches {
-  ($lt: lifetime) => { impl Iterator<Item = NodeMatch<$lt, SgLang>> };
-}
-macro_rules! Diffs {
-  ($lt: lifetime) => { impl Iterator<Item = Diff<$lt>> };
-}
-
 pub struct InteractivePrinter<P: Printer> {
   accept_all: bool,
   from_stdin: bool,
@@ -73,20 +62,19 @@ impl<P: Printer> InteractivePrinter<P> {
 }
 
 impl<P: Printer> Printer for InteractivePrinter<P> {
-  fn print_rule<'a>(
+  fn print_rule(
     &mut self,
-    matches: Matches!('a),
+    matches: Vec<NodeMatch>,
     file: SimpleFile<Cow<str>, &String>,
     rule: &RuleConfig<SgLang>,
   ) -> Result<()> {
     utils::run_in_alternate_screen(|| {
-      let matches: Vec<_> = matches.collect();
       let first_match = match matches.first() {
         Some(n) => n.start_pos().line(),
         None => return Ok(()),
       };
       let file_path = PathBuf::from(file.name().to_string());
-      self.inner.print_rule(matches.into_iter(), file, rule)?;
+      self.inner.print_rule(matches, file, rule)?;
       let resp = self.prompt_view();
       if resp == 'q' {
         Err(anyhow::anyhow!("Exit interactive editing"))
@@ -99,14 +87,14 @@ impl<P: Printer> Printer for InteractivePrinter<P> {
     })
   }
 
-  fn print_matches<'a>(&mut self, matches: Matches!('a), path: &Path) -> Result<()> {
+  fn print_matches(&mut self, matches: Vec<NodeMatch>, path: &Path) -> Result<()> {
     utils::run_in_alternate_screen(|| print_matches_and_confirm_next(self, matches, path))
   }
 
-  fn print_diffs<'a>(&mut self, diffs: Diffs!('a), path: &Path) -> Result<()> {
+  fn print_diffs(&mut self, diffs: Vec<Diff>, path: &Path) -> Result<()> {
     let path = path.to_path_buf();
-    let (confirmed, all) =
-      print_diffs_interactive(self, &path, diffs.map(|d| (d, None)).collect())?;
+    let diffs = diffs.into_iter().map(|d| (d, None)).collect();
+    let (confirmed, all) = print_diffs_interactive(self, &path, diffs)?;
     self.rewrite_action(confirmed, &path)?;
     if all {
       self.accept_all = true;
@@ -178,7 +166,7 @@ fn print_diff_and_prompt_action(
     if let Some(rule) = rule {
       printer.print_rule_diffs(vec![(diff.clone(), rule)], path)?;
     } else {
-      printer.print_diffs(std::iter::once(diff.clone()), path)?;
+      printer.print_diffs(vec![diff.clone()], path)?;
     }
     match interactive.prompt_edit() {
       'y' => Ok((true, false)),
@@ -195,18 +183,17 @@ fn print_diff_and_prompt_action(
   })
 }
 
-fn print_matches_and_confirm_next<'a>(
+fn print_matches_and_confirm_next(
   interactive: &mut InteractivePrinter<impl Printer>,
-  matches: Matches!('a),
+  matches: Vec<NodeMatch>,
   path: &Path,
 ) -> Result<()> {
   let printer = &mut interactive.inner;
-  let matches: Vec<_> = matches.collect();
   let first_match = match matches.first() {
     Some(n) => n.start_pos().line(),
     None => return Ok(()),
   };
-  printer.print_matches(matches.into_iter(), path)?;
+  printer.print_matches(matches, path)?;
   let resp = interactive.prompt_view();
   if resp == 'q' {
     Err(anyhow::anyhow!("Exit interactive editing"))
diff --git a/crates/cli/src/print/json_print.rs b/crates/cli/src/print/json_print.rs
index f979d508..72771b74 100644
--- a/crates/cli/src/print/json_print.rs
+++ b/crates/cli/src/print/json_print.rs
@@ -1,13 +1,12 @@
 use crate::lang::SgLang;
 use ast_grep_config::{RuleConfig, Severity};
-use ast_grep_core::{meta_var::MetaVariable, Node as SgNode, NodeMatch as SgNodeMatch, StrDoc};
+use ast_grep_core::{meta_var::MetaVariable, Node as SgNode, StrDoc};
 
-type NodeMatch<'a, L> = SgNodeMatch<'a, StrDoc<L>>;
 type Node<'a, L> = SgNode<'a, StrDoc<L>>;
 
 use std::collections::HashMap;
 
-use super::{Diff, Printer};
+use super::{Diff, NodeMatch, Printer};
 use anyhow::Result;
 use clap::ValueEnum;
 use codespan_reporting::files::SimpleFile;
@@ -17,14 +16,6 @@ use std::borrow::Cow;
 use std::io::{Stdout, Write};
 use std::path::Path;
 
-// add this macro because neither trait_alias nor type_alias_impl is supported.
-macro_rules! Matches {
-  ($lt: lifetime) => { impl Iterator<Item = NodeMatch<$lt, SgLang>> };
-}
-macro_rules! Diffs {
-  ($lt: lifetime) => { impl Iterator<Item = Diff<$lt>> };
-}
-
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 /// Zero-based character position in a file.
@@ -92,7 +83,7 @@ struct MetaVariables<'a> {
   multi: HashMap<String, Vec<MatchNode<'a>>>,
   transformed: HashMap<String, String>,
 }
-fn from_env<'a>(nm: &NodeMatch<'a, SgLang>) -> Option<MetaVariables<'a>> {
+fn from_env<'a>(nm: &NodeMatch<'a>) -> Option<MetaVariables<'a>> {
   let env = nm.get_env();
   let mut vars = env.get_matched_variables().peekable();
   vars.peek()?;
@@ -155,7 +146,7 @@ fn get_range(n: &Node<'_, SgLang>) -> Range {
 }
 
 impl<'a> MatchJSON<'a> {
-  fn new(nm: NodeMatch<'a, SgLang>, path: &'a str, context: (u16, u16)) -> Self {
+  fn new(nm: NodeMatch<'a>, path: &'a str, context: (u16, u16)) -> Self {
     let display = nm.display_context(context.0 as usize, context.1 as usize);
     let lines = format!("{}{}{}", display.leading, display.matched, display.trailing);
     MatchJSON {
@@ -181,7 +172,7 @@ impl<'a> MatchJSON<'a> {
     ret
   }
 }
-fn get_labels<'a>(nm: &NodeMatch<'a, SgLang>) -> Option<Vec<MatchNode<'a>>> {
+fn get_labels<'a>(nm: &NodeMatch<'a>) -> Option<Vec<MatchNode<'a>>> {
   let env = nm.get_env();
   let labels = env.get_labels("secondary")?;
   Some(
@@ -208,7 +199,7 @@ struct RuleMatchJSON<'a> {
   labels: Option<Vec<MatchNode<'a>>>,
 }
 impl<'a> RuleMatchJSON<'a> {
-  fn new(nm: NodeMatch<'a, SgLang>, path: &'a str, rule: &'a RuleConfig<SgLang>) -> Self {
+  fn new(nm: NodeMatch<'a>, path: &'a str, rule: &'a RuleConfig<SgLang>) -> Self {
     let message = rule.get_message(&nm);
     let labels = get_labels(&nm);
     let matched = MatchJSON::new(nm, path, (0, 0));
@@ -325,33 +316,39 @@ impl<W: Write> JSONPrinter<W> {
 }
 
 impl<W: Write> Printer for JSONPrinter<W> {
-  fn print_rule<'a>(
+  fn print_rule(
     &mut self,
-    matches: Matches!('a),
+    matches: Vec<NodeMatch>,
     file: SimpleFile<Cow<str>, &String>,
     rule: &RuleConfig<SgLang>,
   ) -> Result<()> {
     let path = file.name();
-    let jsons = matches.map(|nm| RuleMatchJSON::new(nm, path, rule));
+    let jsons = matches
+      .into_iter()
+      .map(|nm| RuleMatchJSON::new(nm, path, rule));
     self.print_docs(jsons)
   }
 
-  fn print_matches<'a>(&mut self, matches: Matches!('a), path: &Path) -> Result<()> {
+  fn print_matches(&mut self, matches: Vec<NodeMatch>, path: &Path) -> Result<()> {
     let path = path.to_string_lossy();
     let context = self.context;
-    let jsons = matches.map(|nm| MatchJSON::new(nm, &path, context));
+    let jsons = matches
+      .into_iter()
+      .map(|nm| MatchJSON::new(nm, &path, context));
     self.print_docs(jsons)
   }
 
-  fn print_diffs<'a>(&mut self, diffs: Diffs!('a), path: &Path) -> Result<()> {
+  fn print_diffs(&mut self, diffs: Vec<Diff>, path: &Path) -> Result<()> {
     let path = path.to_string_lossy();
     let context = self.context;
-    let jsons = diffs.map(|diff| MatchJSON::diff(diff, &path, context));
+    let jsons = diffs
+      .into_iter()
+      .map(|diff| MatchJSON::diff(diff, &path, context));
     self.print_docs(jsons)
   }
   fn print_rule_diffs(
     &mut self,
-    diffs: Vec<(Diff<'_>, &RuleConfig<SgLang>)>,
+    diffs: Vec<(Diff, &RuleConfig<SgLang>)>,
     path: &Path,
   ) -> Result<()> {
     let path = path.to_string_lossy();
@@ -412,9 +409,7 @@ mod test {
     for style in [JsonStyle::Pretty, JsonStyle::Compact] {
       let mut printer = make_test_printer(style);
       printer.before_print().unwrap();
-      printer
-        .print_matches(std::iter::empty(), "test.tsx".as_ref())
-        .unwrap();
+      printer.print_matches(vec![], "test.tsx".as_ref()).unwrap();
       printer.after_print().unwrap();
       assert_eq!(get_text(&printer), "[]\n");
     }
@@ -451,7 +446,7 @@ mod test {
       // heading is required for CI
       let mut printer = make_test_printer(JsonStyle::Pretty);
       let grep = SgLang::from(SupportLang::Tsx).ast_grep(source);
-      let matches = grep.root().find_all(pattern);
+      let matches = grep.root().find_all(pattern).collect();
       printer.before_print().unwrap();
       printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
       printer.after_print().unwrap();
@@ -470,7 +465,9 @@ mod test {
       let grep = lang.ast_grep(source);
       let matches = grep.root().find_all(pattern);
       let fixer = Fixer::from_str(replace, &lang).expect("should work");
-      let diffs = matches.map(|m| Diff::generate(m, &pattern, &fixer));
+      let diffs = matches
+        .map(|m| Diff::generate(m, &pattern, &fixer))
+        .collect();
       printer.before_print().unwrap();
       printer.print_diffs(diffs, "test.tsx".as_ref()).unwrap();
       printer.after_print().unwrap();
@@ -512,7 +509,7 @@ rule:
       let mut printer = make_test_printer(JsonStyle::Pretty);
       let grep = SgLang::from(SupportLang::Tsx).ast_grep(&source);
       let rule = make_rule(pattern);
-      let matches = grep.root().find_all(&rule.matcher);
+      let matches = grep.root().find_all(&rule.matcher).collect();
       printer.before_print().unwrap();
       let file = SimpleFile::new(Cow::Borrowed("test.ts"), &source);
       printer.print_rule(matches, file, &rule).unwrap();
@@ -529,7 +526,7 @@ rule:
     let mut printer = make_test_printer(JsonStyle::Pretty);
     let lang = SgLang::from(SupportLang::Tsx);
     let grep = lang.ast_grep("console.log(123)");
-    let matches = grep.root().find_all("console.log($A)");
+    let matches = grep.root().find_all("console.log($A)").collect();
     printer.before_print().unwrap();
     printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
     printer.after_print().unwrap();
@@ -548,7 +545,7 @@ rule:
     let mut printer = make_test_printer(JsonStyle::Compact);
     let lang = SgLang::from(SupportLang::Tsx);
     let grep = lang.ast_grep("console.log(1, 2, 3)");
-    let matches = grep.root().find_all("console.log($$$A)");
+    let matches = grep.root().find_all("console.log($$$A)").collect();
     printer.before_print().unwrap();
     printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
     printer.after_print().unwrap();
@@ -565,7 +562,7 @@ rule:
     for &(source, pattern, _, note) in MATCHES_CASES {
       let mut printer = make_test_printer(JsonStyle::Stream);
       let grep = SgLang::from(SupportLang::Tsx).ast_grep(source);
-      let matches = grep.root().find_all(pattern);
+      let matches = grep.root().find_all(pattern).collect();
       printer.before_print().unwrap();
       printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
       printer.after_print().unwrap();
@@ -594,7 +591,7 @@ transform:
     let mut printer = make_test_printer(JsonStyle::Compact);
     let rule = get_rule_config(&format!("pattern: console.log($A)\n{}", TRANSFORM_TEXT));
     let grep = SgLang::from(SupportLang::TypeScript).ast_grep("console.log(123)");
-    let matches = grep.root().find_all(&rule.matcher);
+    let matches = grep.root().find_all(&rule.matcher).collect();
     printer.before_print().unwrap();
     printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
     printer.after_print().unwrap();
diff --git a/crates/cli/src/print/mod.rs b/crates/cli/src/print/mod.rs
index ffbcb4d1..ad919bd3 100644
--- a/crates/cli/src/print/mod.rs
+++ b/crates/cli/src/print/mod.rs
@@ -20,28 +20,20 @@ pub use colored_print::{print_diff, ColoredPrinter, Heading, PrintStyles, Report
 pub use interactive_print::InteractivePrinter;
 pub use json_print::{JSONPrinter, JsonStyle};
 
-type NodeMatch<'a, L> = SgNodeMatch<'a, StrDoc<L>>;
-
-// add this macro because neither trait_alias nor type_alias_impl is supported.
-macro_rules! Matches {
-  ($lt: lifetime) => { impl Iterator<Item = NodeMatch<$lt, SgLang>> };
-}
-macro_rules! Diffs {
-  ($lt: lifetime) => { impl Iterator<Item = Diff<$lt>> };
-}
+type NodeMatch<'a> = SgNodeMatch<'a, StrDoc<SgLang>>;
 
 pub trait Printer {
-  fn print_rule<'a>(
+  fn print_rule(
     &mut self,
-    matches: Matches!('a),
+    matches: Vec<NodeMatch>,
     file: SimpleFile<Cow<str>, &String>,
     rule: &RuleConfig<SgLang>,
   ) -> Result<()>;
-  fn print_matches<'a>(&mut self, matches: Matches!('a), path: &Path) -> Result<()>;
-  fn print_diffs<'a>(&mut self, diffs: Diffs!('a), path: &Path) -> Result<()>;
+  fn print_matches(&mut self, matches: Vec<NodeMatch>, path: &Path) -> Result<()>;
+  fn print_diffs(&mut self, diffs: Vec<Diff>, path: &Path) -> Result<()>;
   fn print_rule_diffs(
     &mut self,
-    diffs: Vec<(Diff<'_>, &RuleConfig<SgLang>)>,
+    diffs: Vec<(Diff, &RuleConfig<SgLang>)>,
     path: &Path,
   ) -> Result<()>;
   /// Run before all printing. One CLI will run this exactly once.
@@ -59,7 +51,7 @@ pub trait Printer {
 #[derive(Clone)]
 pub struct Diff<'n> {
   /// the matched node
-  pub node_match: NodeMatch<'n, SgLang>,
+  pub node_match: NodeMatch<'n>,
   /// string content for the replacement
   pub replacement: Cow<'n, str>,
   pub range: std::ops::Range<usize>,
@@ -67,7 +59,7 @@ pub struct Diff<'n> {
 
 impl<'n> Diff<'n> {
   pub fn generate(
-    node_match: NodeMatch<'n, SgLang>,
+    node_match: NodeMatch<'n>,
     matcher: &impl Matcher<SgLang>,
     rewrite: &Fixer<SgLang>,
   ) -> Self {
diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs
index de2b88b3..d8effae0 100644
--- a/crates/cli/src/run.rs
+++ b/crates/cli/src/run.rs
@@ -326,9 +326,9 @@ fn match_one_file(
   let matches = grep.root().find_all(matcher);
   if let Some(rewrite) = rewrite {
     let diffs = matches.map(|m| Diff::generate(m, matcher, rewrite));
-    printer.print_diffs(diffs, path)
+    printer.print_diffs(diffs.collect(), path)
   } else {
-    printer.print_matches(matches, path)
+    printer.print_matches(matches.collect(), path)
   }
 }
 
diff --git a/crates/cli/src/scan.rs b/crates/cli/src/scan.rs
index 07ae2110..0cc07399 100644
--- a/crates/cli/src/scan.rs
+++ b/crates/cli/src/scan.rs
@@ -295,10 +295,10 @@ fn match_rule_on_file(
   file_content: &String,
   reporter: &mut impl Printer,
 ) -> Result<()> {
-  let matches = matches.into_iter();
   let file = SimpleFile::new(path.to_string_lossy(), file_content);
   if let Some(fixer) = &rule.matcher.fixer {
     let diffs = matches
+      .into_iter()
       .map(|m| (Diff::generate(m, &rule.matcher, fixer), rule))
       .collect();
     reporter.print_rule_diffs(diffs, path)?;


Test patch:
diff --git a/crates/cli/src/print/colored_print/test.rs b/crates/cli/src/print/colored_print/test.rs
index e8e008a8..e77994a5 100644
--- a/crates/cli/src/print/colored_print/test.rs
+++ b/crates/cli/src/print/colored_print/test.rs
@@ -48,7 +48,7 @@ fn test_print_matches() {
     // heading is required for CI
     let mut printer = make_test_printer().heading(Heading::Always);
     let grep = SgLang::from(SupportLang::Tsx).ast_grep(source);
-    let matches = grep.root().find_all(pattern);
+    let matches = grep.root().find_all(pattern).collect();
     printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
     let expected = source
       .lines()
@@ -68,7 +68,7 @@ fn test_print_matches_without_heading() {
   for &(source, pattern, note) in MATCHES_CASES {
     let mut printer = make_test_printer().heading(Heading::Never);
     let grep = SgLang::from(SupportLang::Tsx).ast_grep(source);
-    let matches = grep.root().find_all(pattern);
+    let matches = grep.root().find_all(pattern).collect();
     printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
     // append heading to expected
     let output = source
@@ -108,7 +108,7 @@ rule:
     .pop()
     .unwrap();
     let matcher = rule.get_matcher(&globals).expect("should parse");
-    let matches = grep.root().find_all(&matcher);
+    let matches = grep.root().find_all(&matcher).collect();
     printer.print_rule(matches, file, &rule).expect("test only");
     let text = get_text(&printer);
     assert!(text.contains("test.tsx"), "{note}");
@@ -158,7 +158,9 @@ fn test_print_diffs() {
     let fixer = Fixer::from_str(rewrite, &lang).expect("should work");
     let grep = lang.ast_grep(source);
     let matches = grep.root().find_all(pattern);
-    let diffs = matches.map(|n| Diff::generate(n, &pattern, &fixer));
+    let diffs = matches
+      .map(|n| Diff::generate(n, &pattern, &fixer))
+      .collect();
     printer.print_diffs(diffs, "test.tsx".as_ref()).unwrap();
     assert!(get_text(&printer).contains(rewrite), "{note}");
   }
@@ -173,7 +175,7 @@ fn test_overlap_print_impl(heading: Heading) {
   let mut printer = make_test_printer().heading(heading).context((1, 1));
   let lang = SgLang::from(SupportLang::Tsx);
   let grep = lang.ast_grep(src);
-  let matches = grep.root().find_all("Some($A)");
+  let matches = grep.root().find_all("Some($A)").collect();
   printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
   let text = get_text(&printer);
   // Overlapped match should only print once.
@@ -198,7 +200,7 @@ fn test_non_overlap_print_impl(heading: Heading) {
   let mut printer = make_test_printer().heading(heading);
   let lang = SgLang::from(SupportLang::Tsx);
   let grep = lang.ast_grep(src);
-  let matches = grep.root().find_all("Some($A)");
+  let matches = grep.root().find_all("Some($A)").collect();
   printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
   let text = get_text(&printer);
   assert_eq!(text.matches("Some(1)").count(), 1);
@@ -267,7 +269,7 @@ fn test_before_after() {
       let mut printer = make_test_printer().context((b, a));
       let lang = SgLang::from(SupportLang::Tsx);
       let grep = lang.ast_grep(src);
-      let matches = grep.root().find_all("Some($A)");
+      let matches = grep.root().find_all("Some($A)").collect();
       printer.print_matches(matches, "test.tsx".as_ref()).unwrap();
       let text = get_text(&printer);
       // Overlapped match should only print once.