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

Issue:
[perf] improve scan perf by skipping files without any rules
`ast-grep scan` now traverses all files in the directory even if they do not have any rule associated to them. We can avoid this overhead by taking language into consideration when iterating over file systems.

Before

![image](https://github.com/user-attachments/assets/3b1944e5-dbb4-4f5d-8913-a515b6a0303b)

After
<img width="1025" alt="image" src="https://github.com/user-attachments/assets/8971fd92-dec1-42bd-97e6-beefe9c630d0">




Discussion / hints:

Fix patch:
diff --git a/crates/cli/src/lang/mod.rs b/crates/cli/src/lang/mod.rs
index 7f810918b6..2d67f642ac 100644
--- a/crates/cli/src/lang/mod.rs
+++ b/crates/cli/src/lang/mod.rs
@@ -84,6 +84,11 @@ impl SgLang {
     let all_types = std::iter::once(self_type).chain(injector_types);
     lang_globs::merge_types(all_types)
   }
+
+  pub fn file_types_for_langs(langs: impl Iterator<Item = Self>) -> Types {
+    let types = langs.map(|lang| lang.augmented_file_type());
+    lang_globs::merge_types(types)
+  }
 }
 
 impl Display for SgLang {
diff --git a/crates/cli/src/scan.rs b/crates/cli/src/scan.rs
index e10911a69e..136acd7aa4 100644
--- a/crates/cli/src/scan.rs
+++ b/crates/cli/src/scan.rs
@@ -21,6 +21,8 @@ use crate::utils::{filter_file_interactive, ContextArgs, InputArgs, OutputArgs,
 use crate::utils::{FileTrace, ScanTrace};
 use crate::utils::{Items, PathWorker, StdInWorker, Worker};
 
+use std::collections::HashSet;
+
 type AstGrep = ast_grep_core::AstGrep<StrDoc<SgLang>>;
 
 #[derive(Args)]
@@ -235,7 +237,11 @@ impl<P: Printer> PathWorker for ScanWithConfig<P> {
     &self.trace.file_trace
   }
   fn build_walk(&self) -> Result<WalkParallel> {
-    self.arg.input.walk()
+    let mut langs = HashSet::new();
+    self.configs.for_each_rule(|rule| {
+      langs.insert(rule.language);
+    });
+    self.arg.input.walk_langs(langs.into_iter())
   }
   fn produce_item(&self, path: &Path) -> Option<Vec<Self::Item>> {
     filter_file_interactive(path, &self.configs, &self.trace)
diff --git a/crates/cli/src/utils/args.rs b/crates/cli/src/utils/args.rs
index e57ccb376a..754eff63f5 100644
--- a/crates/cli/src/utils/args.rs
+++ b/crates/cli/src/utils/args.rs
@@ -84,6 +84,19 @@ impl InputArgs {
     )
   }
 
+  pub fn walk_langs(&self, langs: impl Iterator<Item = SgLang>) -> Result<WalkParallel> {
+    let types = SgLang::file_types_for_langs(langs);
+    let threads = self.get_threads();
+    Ok(
+      NoIgnore::disregard(&self.no_ignore)
+        .walk(&self.paths)
+        .threads(threads)
+        .follow_links(self.follow)
+        .types(types)
+        .build_parallel(),
+    )
+  }
+
   pub fn walk_lang(&self, lang: SgLang) -> WalkParallel {
     let threads = self.get_threads();
     NoIgnore::disregard(&self.no_ignore)


Test patch:
diff --git a/crates/cli/tests/scan_test.rs b/crates/cli/tests/scan_test.rs
index 620e572ca9..58adc5d50c 100644
--- a/crates/cli/tests/scan_test.rs
+++ b/crates/cli/tests/scan_test.rs
@@ -134,12 +134,13 @@ fn test_sg_scan_html() -> Result<()> {
   ])?;
   Command::cargo_bin("sg")?
     .current_dir(dir.path())
-    .args(["scan", "-r", "rule.yml"])
+    .args(["scan", "-r", "rule.yml", "--inspect=summary"])
     .assert()
     .success()
     .stdout(contains("on-rule"))
     .stdout(contains("script"))
-    .stdout(contains("rule-3").not());
+    .stdout(contains("rule-3").not())
+    .stderr(contains("scannedFileCount=1"));
   Ok(())
 }