11#![ allow( clippy:: print_stdout) ]
22
33use std:: {
4- collections:: BTreeSet ,
54 fmt:: Write as _,
65 fs,
76 io:: { self , Write as _} ,
@@ -10,6 +9,7 @@ use std::{
109} ;
1110
1211use convert_case:: { Case , Casing } ;
12+ use rustc_hash:: FxHashSet ;
1313use syn:: { Expr , ExprIf , File , Pat , Path as SynPath , Stmt } ; // keep syn in scope for parse_file used elsewhere
1414
1515fn main ( ) -> io:: Result < ( ) > {
@@ -35,32 +35,35 @@ pub fn generate_rule_runner_impls() -> io::Result<()> {
3535
3636 for rule in & rule_entries {
3737 // Try to open the rule source file and use syn to detect node types
38- let mut detected_types: BTreeSet < String > = BTreeSet :: new ( ) ;
38+ let mut detected_types: NodeTypeSet = NodeTypeSet :: new ( ) ;
3939 if let Some ( src_path) = find_rule_source_file ( & root, rule)
4040 && let Ok ( src_contents) = fs:: read_to_string ( & src_path)
4141 && let Ok ( file) = syn:: parse_file ( & src_contents)
42- && let Some ( bitset ) = detect_top_level_node_types ( & file, rule)
42+ && let Some ( node_types ) = detect_top_level_node_types ( & file, rule)
4343 {
44- detected_types. extend ( bitset ) ;
44+ detected_types. extend ( node_types ) ;
4545 }
4646
4747 let has_detected = !detected_types. is_empty ( ) ;
4848 let ( node_types_init, any_node_type) = if has_detected {
49- // Map variant name to AstType constant path (AstType::Variant)
50- let type_idents: Vec < String > =
51- detected_types. into_iter ( ) . map ( |v| format ! ( "AstType::{v}" ) ) . collect ( ) ;
52- ( format ! ( "AstTypesBitset::from_types(&[{}])" , type_idents. join( ", " ) ) , false )
49+ ( detected_types. to_ast_type_bitset_string ( ) , false )
5350 } else {
5451 ( "AstTypesBitset::new()" . to_string ( ) , true )
5552 } ;
5653
5754 write ! (
5855 out,
59- "impl RuleRunner for crate::rules::{plugin_module}::{rule_module}::{rule_struct} {{\n const NODE_TYPES: &AstTypesBitset = &{node_types_init};\n const ANY_NODE_TYPE: bool = {any_node_type};\n \n }}\n \n " ,
56+ r#"
57+ impl RuleRunner for crate::rules::{plugin_module}::{rule_module}::{rule_struct} {{
58+ const NODE_TYPES: &AstTypesBitset = &{node_types_init};
59+ const ANY_NODE_TYPE: bool = {any_node_type};
60+ }}
61+ "# ,
6062 plugin_module = rule. plugin_module_name,
6163 rule_module = rule. rule_module_name,
6264 rule_struct = rule. rule_struct_name( ) ,
63- ) . unwrap ( ) ;
65+ )
66+ . unwrap ( ) ;
6467 }
6568
6669 let formatted_out = rust_fmt ( & out) ;
@@ -147,23 +150,60 @@ fn get_all_rules(contents: &str) -> io::Result<Vec<RuleEntry<'_>>> {
147150 Ok ( rule_entries)
148151}
149152
153+ /// A set of AstKind variants, used for storing the unique node types detected in a rule,
154+ /// or a portion of the rule file.
155+ struct NodeTypeSet {
156+ node_types : FxHashSet < String > ,
157+ }
158+
159+ impl NodeTypeSet {
160+ /// Create a new set of node variants
161+ fn new ( ) -> Self {
162+ Self { node_types : FxHashSet :: default ( ) }
163+ }
164+
165+ /// Insert a variant into the set
166+ fn insert ( & mut self , node_type_variant : String ) {
167+ self . node_types . insert ( node_type_variant) ;
168+ }
169+
170+ /// Returns `true` if there are no node types in the set.
171+ fn is_empty ( & self ) -> bool {
172+ self . node_types . is_empty ( )
173+ }
174+
175+ /// Extend the set with another set of node types.
176+ fn extend ( & mut self , other : NodeTypeSet ) {
177+ self . node_types . extend ( other. node_types ) ;
178+ }
179+
180+ /// Returns the generated code string to initialize an `AstTypesBitset` with the variants
181+ /// in this set.
182+ fn to_ast_type_bitset_string ( & self ) -> String {
183+ let mut variants: Vec < String > = self . node_types . iter ( ) . cloned ( ) . collect ( ) ;
184+ variants. sort_unstable ( ) ;
185+ let type_idents: Vec < String > =
186+ variants. into_iter ( ) . map ( |v| format ! ( "AstType::{v}" ) ) . collect ( ) ;
187+ format ! ( "AstTypesBitset::from_types(&[{}])" , type_idents. join( ", " ) )
188+ }
189+ }
190+
150191/// Detect the top-level node types used in a lint rule file by analyzing the Rust AST with `syn`.
151192/// Returns `Some(bitset)` if at least one node type can be determined, otherwise `None`.
152- fn detect_top_level_node_types ( file : & File , rule : & RuleEntry ) -> Option < BTreeSet < String > > {
193+ fn detect_top_level_node_types ( file : & File , rule : & RuleEntry ) -> Option < NodeTypeSet > {
153194 let rule_impl = find_rule_impl_block ( file, & rule. rule_struct_name ( ) ) ?;
154195 let run_func = find_impl_function ( rule_impl, "run" ) ?;
155196
156- let variants: BTreeSet < String > = if let Some ( det) = IfElseKindDetector :: from_run_func ( run_func)
157- {
158- det. variants
197+ let node_types = if let Some ( node_types) = IfElseKindDetector :: from_run_func ( run_func) {
198+ node_types
159199 } else {
160200 return None ;
161201 } ;
162- if variants . is_empty ( ) {
202+ if node_types . is_empty ( ) {
163203 return None ;
164204 }
165205
166- Some ( variants )
206+ Some ( node_types )
167207}
168208
169209fn find_rule_impl_block < ' a > ( file : & ' a File , rule_struct_name : & str ) -> Option < & ' a syn:: ItemImpl > {
@@ -194,27 +234,83 @@ fn find_impl_function<'a>(imp: &'a syn::ItemImpl, func_name: &str) -> Option<&'a
194234
195235/// Detects top-level `if let AstKind::... = node.kind()` patterns in the `run` method.
196236struct IfElseKindDetector {
197- variants : BTreeSet < String > ,
237+ node_types : NodeTypeSet ,
198238}
199239
200240impl IfElseKindDetector {
201- fn from_run_func ( run_func : & syn:: ImplItemFn ) -> Option < Self > {
241+ fn from_run_func ( run_func : & syn:: ImplItemFn ) -> Option < NodeTypeSet > {
202242 // Only consider when the body has exactly one top-level statement and it's an `if`.
203243 let block = & run_func. block ;
204244 if block. stmts . len ( ) != 1 {
205245 return None ;
206246 }
207247 let stmt = & block. stmts [ 0 ] ;
208248 let Stmt :: Expr ( Expr :: If ( ifexpr) , _) = stmt else { return None } ;
209- let mut variants = BTreeSet :: new ( ) ;
210- let result = collect_if_chain_variants ( ifexpr, & mut variants ) ;
211- if result == CollectionResult :: Incomplete || variants . is_empty ( ) {
249+ let mut detector = Self { node_types : NodeTypeSet :: new ( ) } ;
250+ let result = detector . collect_if_chain_variants ( ifexpr) ;
251+ if result == CollectionResult :: Incomplete || detector . node_types . is_empty ( ) {
212252 return None ;
213253 }
214- Some ( Self { variants } )
254+ Some ( detector. node_types )
255+ }
256+
257+ /// Collects AstKind variants from an if-else chain of `if let AstKind::Xxx(..) = node.kind()`.
258+ /// Returns `true` if all syntax was recognized as supported, otherwise `false`, indicating that
259+ /// the variants collected may be incomplete and should not be treated as valid.
260+ fn collect_if_chain_variants ( & mut self , ifexpr : & ExprIf ) -> CollectionResult {
261+ // Extract variants from condition like `if let AstKind::Xxx(..) = node.kind()`.
262+ if self . extract_variants_from_if_let_condition ( & ifexpr. cond ) == CollectionResult :: Incomplete
263+ {
264+ // If syntax is not recognized, return Incomplete.
265+ return CollectionResult :: Incomplete ;
266+ }
267+ // Walk else-if chain.
268+ if let Some ( ( _, else_branch) ) = & ifexpr. else_branch {
269+ match & * * else_branch {
270+ Expr :: If ( nested) => self . collect_if_chain_variants ( nested) ,
271+ // plain `else { ... }` should default to any node type
272+ _ => CollectionResult :: Incomplete ,
273+ }
274+ } else {
275+ CollectionResult :: Complete
276+ }
277+ }
278+
279+ /// Extracts AstKind variants from an `if let` condition like `if let AstKind::Xxx(..) = node.kind()`.
280+ fn extract_variants_from_if_let_condition ( & mut self , cond : & Expr ) -> CollectionResult {
281+ let Expr :: Let ( let_expr) = cond else { return CollectionResult :: Incomplete } ;
282+ // RHS must be `node.kind()`
283+ if is_node_kind_call ( & let_expr. expr ) {
284+ self . extract_variants_from_pat ( & let_expr. pat )
285+ } else {
286+ CollectionResult :: Incomplete
287+ }
288+ }
289+
290+ fn extract_variants_from_pat ( & mut self , pat : & Pat ) -> CollectionResult {
291+ match pat {
292+ Pat :: Or ( orpat) => {
293+ for p in & orpat. cases {
294+ if self . extract_variants_from_pat ( p) == CollectionResult :: Incomplete {
295+ return CollectionResult :: Incomplete ;
296+ }
297+ }
298+ CollectionResult :: Complete
299+ }
300+ Pat :: TupleStruct ( ts) => {
301+ if let Some ( variant) = astkind_variant_from_path ( & ts. path ) {
302+ self . node_types . insert ( variant) ;
303+ CollectionResult :: Complete
304+ } else {
305+ CollectionResult :: Incomplete
306+ }
307+ }
308+ _ => CollectionResult :: Incomplete ,
309+ }
215310 }
216311}
217312
313+ /// Result of attempting to collect node type variants.
218314#[ derive( Debug , PartialEq , Eq ) ]
219315enum CollectionResult {
220316 /// All syntax recognized as supported, variants collected should be complete.
@@ -224,37 +320,6 @@ enum CollectionResult {
224320 Incomplete ,
225321}
226322
227- /// Collects AstKind variants from an if-else chain of `if let AstKind::Xxx(..) = node.kind()`.
228- /// Returns `true` if all syntax was recognized as supported, otherwise `false`, indicating that
229- /// the variants collected may be incomplete and should not be treated as valid.
230- fn collect_if_chain_variants ( ifexpr : & ExprIf , out : & mut BTreeSet < String > ) -> CollectionResult {
231- // Extract variants from condition like `if let AstKind::Xxx(..) = node.kind()`.
232- if extract_variants_from_if_condition ( & ifexpr. cond , out) == CollectionResult :: Incomplete {
233- // If syntax is not recognized, return Incomplete.
234- return CollectionResult :: Incomplete ;
235- }
236- // Walk else-if chain.
237- if let Some ( ( _, else_branch) ) = & ifexpr. else_branch {
238- match & * * else_branch {
239- Expr :: If ( nested) => collect_if_chain_variants ( nested, out) ,
240- // plain `else { ... }` should default to any node type
241- _ => CollectionResult :: Incomplete ,
242- }
243- } else {
244- CollectionResult :: Complete
245- }
246- }
247-
248- fn extract_variants_from_if_condition ( cond : & Expr , out : & mut BTreeSet < String > ) -> CollectionResult {
249- let Expr :: Let ( let_expr) = cond else { return CollectionResult :: Incomplete } ;
250- // RHS must be `node.kind()`
251- if is_node_kind_call ( & let_expr. expr ) {
252- extract_variants_from_pat ( & let_expr. pat , out)
253- } else {
254- CollectionResult :: Incomplete
255- }
256- }
257-
258323fn is_node_kind_call ( expr : & Expr ) -> bool {
259324 if let Expr :: MethodCall ( mc) = expr
260325 && mc. method == "kind"
@@ -266,28 +331,6 @@ fn is_node_kind_call(expr: &Expr) -> bool {
266331 false
267332}
268333
269- fn extract_variants_from_pat ( pat : & Pat , out : & mut BTreeSet < String > ) -> CollectionResult {
270- match pat {
271- Pat :: Or ( orpat) => {
272- for p in & orpat. cases {
273- if extract_variants_from_pat ( p, out) == CollectionResult :: Incomplete {
274- return CollectionResult :: Incomplete ;
275- }
276- }
277- CollectionResult :: Complete
278- }
279- Pat :: TupleStruct ( ts) => {
280- if let Some ( variant) = astkind_variant_from_path ( & ts. path ) {
281- out. insert ( variant) ;
282- CollectionResult :: Complete
283- } else {
284- CollectionResult :: Incomplete
285- }
286- }
287- _ => CollectionResult :: Incomplete ,
288- }
289- }
290-
291334/// Extract AstKind variant from something like `AstKind::Variant`
292335fn astkind_variant_from_path ( path : & SynPath ) -> Option < String > {
293336 // Expect `AstKind::Variant`
0 commit comments