Repository: ast-grep/ast-grep (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">



Pull request description:
feat: only scan rule senstive files
fix #1635

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

## Summary by CodeRabbit

- **New Features**
	- Introduced a new method to retrieve file types for multiple languages, enhancing language handling.
	- Added a language filtering mechanism in the scanning process to improve efficiency by processing only relevant languages.
	- Implemented a new method for parallel walking of paths based on specified languages.

- **Bug Fixes**
	- No bug fixes were introduced in this release. 

- **Documentation**
	- No documentation updates were made in this release.

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

Patch:
diff --git a/crates/cli/src/lang/mod.rs b/crates/cli/src/lang/mod.rs
index 7f810918..2d67f642 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 e10911a6..136acd7a 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 e57ccb37..754eff63 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 620e572c..58adc5d5 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(())
 }