@@ -89,6 +89,21 @@ impl ProjectConfig {
8989 let global_rules = find_util_rules ( self ) ?;
9090 read_directory_yaml ( self , global_rules, rule_overwrite)
9191 }
92+
93+ /// Create a rule finding closure that can be used by LSP or other consumers
94+ /// This allows decoupling the rule finding logic from the specific consumers
95+ pub fn make_rule_finder < L > ( self ) -> impl Fn ( ) -> anyhow:: Result < RuleCollection < L > > + Send + Sync + ' static
96+ where
97+ L : ast_grep_core:: Language + serde:: de:: DeserializeOwned + Clone + std:: cmp:: Eq + Send + Sync + ' static ,
98+ {
99+ move || {
100+ let global_rules = find_util_rules_generic :: < L > ( & self . project_dir , & self . util_dirs ) ?;
101+ let configs = read_directory_yaml_generic :: < L > ( & self . project_dir , & self . rule_dirs , global_rules) ?;
102+ let collection = RuleCollection :: try_new ( configs) . context ( EC :: GlobPattern ) ?;
103+ Ok ( collection)
104+ }
105+ }
106+
92107 /// returns a Result of Result.
93108 /// The inner Result is for configuration not found, or ProjectNotExist
94109 /// The outer Result is for definitely wrong config.
@@ -160,6 +175,39 @@ fn find_util_rules(config: &ProjectConfig) -> Result<GlobalRules> {
160175 Ok ( ret)
161176}
162177
178+ /// Generic version of find_util_rules that works with any language type
179+ fn find_util_rules_generic < L > (
180+ project_dir : & Path ,
181+ util_dirs : & Option < Vec < PathBuf > > ,
182+ ) -> Result < GlobalRules >
183+ where
184+ L : ast_grep_core:: Language + serde:: de:: DeserializeOwned + Clone + std:: cmp:: Eq + Send + Sync + ' static ,
185+ {
186+ let Some ( mut walker) = build_util_walker ( project_dir, util_dirs) else {
187+ return Ok ( GlobalRules :: default ( ) ) ;
188+ } ;
189+ let mut utils = vec ! [ ] ;
190+ let walker = walker. types ( config_file_type ( ) ) . build ( ) ;
191+ for dir in walker {
192+ let config_file = dir. with_context ( || EC :: WalkRuleDir ( PathBuf :: new ( ) ) ) ?;
193+ // file_type is None only if it is stdin, safe to panic here
194+ if !config_file
195+ . file_type ( )
196+ . expect ( "file type should be available for non-stdin" )
197+ . is_file ( )
198+ {
199+ continue ;
200+ }
201+ let path = config_file. path ( ) ;
202+ let file = read_to_string ( path) ?;
203+ let new_configs = from_str ( & file) ?;
204+ utils. push ( new_configs) ;
205+ }
206+
207+ let ret = DeserializeEnv :: < L > :: parse_global_utils ( utils) . context ( EC :: InvalidGlobalUtils ) ?;
208+ Ok ( ret)
209+ }
210+
163211fn read_directory_yaml (
164212 config : & ProjectConfig ,
165213 global_rules : GlobalRules ,
@@ -204,6 +252,39 @@ fn read_directory_yaml(
204252 Ok ( ( collection, trace) )
205253}
206254
255+ /// Generic version of read_directory_yaml that works with any language type
256+ fn read_directory_yaml_generic < L > (
257+ project_dir : & Path ,
258+ rule_dirs : & [ PathBuf ] ,
259+ global_rules : GlobalRules ,
260+ ) -> Result < Vec < RuleConfig < L > > >
261+ where
262+ L : ast_grep_core:: Language + serde:: de:: DeserializeOwned + Clone + std:: cmp:: Eq + Send + Sync + ' static ,
263+ {
264+ let mut configs = vec ! [ ] ;
265+ for dir in rule_dirs {
266+ let dir_path = project_dir. join ( dir) ;
267+ let walker = WalkBuilder :: new ( & dir_path)
268+ . types ( config_file_type ( ) )
269+ . build ( ) ;
270+ for dir in walker {
271+ let config_file = dir. with_context ( || EC :: WalkRuleDir ( dir_path. clone ( ) ) ) ?;
272+ // file_type is None only if it is stdin, safe to panic here
273+ if !config_file
274+ . file_type ( )
275+ . expect ( "file type should be available for non-stdin" )
276+ . is_file ( )
277+ {
278+ continue ;
279+ }
280+ let path = config_file. path ( ) ;
281+ let new_configs = read_rule_file_generic :: < L > ( path, Some ( & global_rules) ) ?;
282+ configs. extend ( new_configs) ;
283+ }
284+ }
285+ Ok ( configs)
286+ }
287+
207288pub fn with_rule_stats (
208289 configs : Vec < RuleConfig < SgLang > > ,
209290) -> Result < ( RuleCollection < SgLang > , RuleTrace ) > {
@@ -231,6 +312,23 @@ pub fn read_rule_file(
231312 parsed. with_context ( || EC :: ParseRule ( path. to_path_buf ( ) ) )
232313}
233314
315+ /// Generic version of read_rule_file that works with any language type
316+ pub fn read_rule_file_generic < L > (
317+ path : & Path ,
318+ global_rules : Option < & GlobalRules > ,
319+ ) -> Result < Vec < RuleConfig < L > > >
320+ where
321+ L : ast_grep_core:: Language + serde:: de:: DeserializeOwned + Clone + std:: cmp:: Eq + Send + Sync + ' static ,
322+ {
323+ let yaml = read_to_string ( path) . with_context ( || EC :: ReadRule ( path. to_path_buf ( ) ) ) ?;
324+ let parsed = if let Some ( globals) = global_rules {
325+ from_yaml_string ( & yaml, globals)
326+ } else {
327+ from_yaml_string ( & yaml, & Default :: default ( ) )
328+ } ;
329+ parsed. with_context ( || EC :: ParseRule ( path. to_path_buf ( ) ) )
330+ }
331+
234332const CONFIG_FILE : & str = "sgconfig.yml" ;
235333
236334/// return None if config file does not exist
0 commit comments