11#![ allow( clippy:: print_stdout) ]
22
3+ use crate :: {
4+ if_else_detector:: IfElseKindDetector ,
5+ node_type_set:: NodeTypeSet ,
6+ rules:: { RuleEntry , find_rule_source_file, get_all_rules} ,
7+ utils:: { find_impl_function, find_rule_impl_block} ,
8+ } ;
39use std:: {
410 fmt:: Write as _,
511 fs,
612 io:: { self , Write as _} ,
7- path:: Path ,
813 process:: { Command , Stdio } ,
914} ;
15+ use syn:: File ;
1016
11- use convert_case:: { Case , Casing } ;
12- use rustc_hash:: FxHashSet ;
13- use syn:: { Expr , ExprIf , File , Pat , Path as SynPath , Stmt } ; // keep syn in scope for parse_file used elsewhere
17+ mod if_else_detector;
18+ mod node_type_set;
19+ mod rules;
20+ mod utils;
1421
1522fn main ( ) -> io:: Result < ( ) > {
1623 generate_rule_runner_impls ( )
@@ -73,120 +80,6 @@ impl RuleRunner for crate::rules::{plugin_module}::{rule_module}::{rule_struct}
7380 Ok ( ( ) )
7481}
7582
76- /// Given a rule entry, attempt to find its corresponding source file path
77- fn find_rule_source_file ( root : & Path , rule : & RuleEntry ) -> Option < std:: path:: PathBuf > {
78- // A rule path corresponds to:
79- // 1) `crates/oxc_linter/src/rules/<plugin>/<rule>.rs`
80- // 2) `crates/oxc_linter/src/rules/<plugin>/<rule>/mod.rs`
81- let rules_path = root. join ( "crates/oxc_linter/src/rules" ) . join ( rule. plugin_module_name ) ;
82-
83- let direct_path = rules_path. join ( format ! ( "{}.rs" , rule. rule_module_name) ) ;
84- if direct_path. exists ( ) {
85- return Some ( direct_path) ;
86- }
87-
88- let mod_path = rules_path. join ( rule. rule_module_name ) . join ( "mod.rs" ) ;
89- if mod_path. exists ( ) {
90- return Some ( mod_path) ;
91- }
92-
93- None
94- }
95-
96- /// Represents a lint rule entry in the `declare_all_lint_rules!` macro.
97- #[ derive( PartialEq , Eq , PartialOrd , Ord ) ]
98- struct RuleEntry < ' e > {
99- /// The module name of the rule's plugin, like `eslint` in `eslint::no_debugger::NoDebugger`.
100- plugin_module_name : & ' e str ,
101- /// The rule's module name, like `no_debugger` in `eslint::no_debugger:NoDebugger`.
102- rule_module_name : & ' e str ,
103- }
104-
105- impl RuleEntry < ' _ > {
106- /// Get the rule's struct name, like `NoDebugger` in `eslint::no_debugger::NoDebugger`.
107- fn rule_struct_name ( & self ) -> String {
108- self . rule_module_name . to_case ( Case :: Pascal )
109- }
110- }
111-
112- /// Parses `crates/oxc_linter/src/rules.rs` to extract all lint rule declarations into a list
113- /// of `RuleEntry`.
114- fn get_all_rules ( contents : & str ) -> io:: Result < Vec < RuleEntry < ' _ > > > {
115- let start_marker = "oxc_macros::declare_all_lint_rules!" ;
116- let start = contents. find ( start_marker) . ok_or_else ( || {
117- std:: io:: Error :: other ( "could not find declare_all_lint_rules macro invocation" )
118- } ) ?;
119-
120- let body = & contents[ start..] ;
121-
122- // Collect (module path, struct name) pairs. Do NOT deduplicate by struct name because
123- // different plugins may have rules with the same struct name.
124- let mut rule_entries = Vec :: new ( ) ;
125- for line in body. lines ( ) . skip ( 1 ) {
126- let line = line. trim ( ) ;
127- if line. contains ( '}' ) {
128- break ;
129- }
130- if line. is_empty ( ) || line. starts_with ( "//" ) {
131- continue ;
132- }
133- if !line. ends_with ( ',' ) {
134- continue ;
135- }
136- let path = & line[ ..line. len ( ) - 1 ] ;
137- let parts = path. split ( "::" ) . collect :: < Vec < _ > > ( ) ;
138- if parts. len ( ) != 2 {
139- continue ;
140- }
141- let Some ( plugin_module_name) = parts. first ( ) else { continue } ;
142- let Some ( rule_module_name) = parts. get ( 1 ) else { continue } ;
143- rule_entries. push ( RuleEntry { plugin_module_name, rule_module_name } ) ;
144- }
145- // Sort deterministically
146- rule_entries. sort_unstable ( ) ;
147-
148- Ok ( rule_entries)
149- }
150-
151- /// A set of AstKind variants, used for storing the unique node types detected in a rule,
152- /// or a portion of the rule file.
153- struct NodeTypeSet {
154- node_types : FxHashSet < String > ,
155- }
156-
157- impl NodeTypeSet {
158- /// Create a new set of node variants
159- fn new ( ) -> Self {
160- Self { node_types : FxHashSet :: default ( ) }
161- }
162-
163- /// Insert a variant into the set
164- fn insert ( & mut self , node_type_variant : String ) {
165- self . node_types . insert ( node_type_variant) ;
166- }
167-
168- /// Returns `true` if there are no node types in the set.
169- fn is_empty ( & self ) -> bool {
170- self . node_types . is_empty ( )
171- }
172-
173- /// Extend the set with another set of node types.
174- fn extend ( & mut self , other : NodeTypeSet ) {
175- self . node_types . extend ( other. node_types ) ;
176- }
177-
178- /// Returns the generated code string to initialize an `AstTypesBitset` with the variants
179- /// in this set.
180- fn to_ast_type_bitset_string ( & self ) -> String {
181- let mut variants: Vec < & str > =
182- self . node_types . iter ( ) . map ( std:: string:: String :: as_str) . collect ( ) ;
183- variants. sort_unstable ( ) ;
184- let type_idents: Vec < String > =
185- variants. into_iter ( ) . map ( |v| format ! ( "AstType::{v}" ) ) . collect ( ) ;
186- format ! ( "AstTypesBitset::from_types(&[{}])" , type_idents. join( ", " ) )
187- }
188- }
189-
19083/// Detect the top-level node types used in a lint rule file by analyzing the Rust AST with `syn`.
19184/// Returns `Some(bitset)` if at least one node type can be determined, otherwise `None`.
19285fn detect_top_level_node_types ( file : & File , rule : & RuleEntry ) -> Option < NodeTypeSet > {
@@ -201,110 +94,6 @@ fn detect_top_level_node_types(file: &File, rule: &RuleEntry) -> Option<NodeType
20194 Some ( node_types)
20295}
20396
204- fn find_rule_impl_block < ' a > ( file : & ' a File , rule_struct_name : & str ) -> Option < & ' a syn:: ItemImpl > {
205- for item in & file. items {
206- let syn:: Item :: Impl ( imp) = item else { continue } ;
207- let ident = match imp. self_ty . as_ref ( ) {
208- syn:: Type :: Path ( p) => p. path . get_ident ( ) ,
209- _ => None ,
210- } ;
211- if ident. is_some_and ( |id| id == rule_struct_name)
212- && imp. trait_ . as_ref ( ) . is_some_and ( |( _, path, _) | path. is_ident ( "Rule" ) )
213- {
214- return Some ( imp) ;
215- }
216- }
217- None
218- }
219-
220- fn find_impl_function < ' a > ( imp : & ' a syn:: ItemImpl , func_name : & str ) -> Option < & ' a syn:: ImplItemFn > {
221- for impl_item in & imp. items {
222- let syn:: ImplItem :: Fn ( func) = impl_item else { continue } ;
223- if func. sig . ident == func_name {
224- return Some ( func) ;
225- }
226- }
227- None
228- }
229-
230- /// Detects top-level `if let AstKind::... = node.kind()` patterns in the `run` method.
231- struct IfElseKindDetector {
232- node_types : NodeTypeSet ,
233- }
234-
235- impl IfElseKindDetector {
236- fn from_run_func ( run_func : & syn:: ImplItemFn ) -> Option < NodeTypeSet > {
237- // Only consider when the body has exactly one top-level statement and it's an `if`.
238- let block = & run_func. block ;
239- if block. stmts . len ( ) != 1 {
240- return None ;
241- }
242- let stmt = & block. stmts [ 0 ] ;
243- let Stmt :: Expr ( Expr :: If ( ifexpr) , _) = stmt else { return None } ;
244- let mut detector = Self { node_types : NodeTypeSet :: new ( ) } ;
245- let result = detector. collect_if_chain_variants ( ifexpr) ;
246- if result == CollectionResult :: Incomplete || detector. node_types . is_empty ( ) {
247- return None ;
248- }
249- Some ( detector. node_types )
250- }
251-
252- /// Collects AstKind variants from an if-else chain of `if let AstKind::Xxx(..) = node.kind()`.
253- /// Returns `true` if all syntax was recognized as supported, otherwise `false`, indicating that
254- /// the variants collected may be incomplete and should not be treated as valid.
255- fn collect_if_chain_variants ( & mut self , ifexpr : & ExprIf ) -> CollectionResult {
256- // Extract variants from condition like `if let AstKind::Xxx(..) = node.kind()`.
257- if self . extract_variants_from_if_let_condition ( & ifexpr. cond ) == CollectionResult :: Incomplete
258- {
259- // If syntax is not recognized, return Incomplete.
260- return CollectionResult :: Incomplete ;
261- }
262- // Walk else-if chain.
263- if let Some ( ( _, else_branch) ) = & ifexpr. else_branch {
264- match & * * else_branch {
265- Expr :: If ( nested) => self . collect_if_chain_variants ( nested) ,
266- // plain `else { ... }` should default to any node type
267- _ => CollectionResult :: Incomplete ,
268- }
269- } else {
270- CollectionResult :: Complete
271- }
272- }
273-
274- /// Extracts AstKind variants from an `if let` condition like `if let AstKind::Xxx(..) = node.kind()`.
275- fn extract_variants_from_if_let_condition ( & mut self , cond : & Expr ) -> CollectionResult {
276- let Expr :: Let ( let_expr) = cond else { return CollectionResult :: Incomplete } ;
277- // RHS must be `node.kind()`
278- if is_node_kind_call ( & let_expr. expr ) {
279- self . extract_variants_from_pat ( & let_expr. pat )
280- } else {
281- CollectionResult :: Incomplete
282- }
283- }
284-
285- fn extract_variants_from_pat ( & mut self , pat : & Pat ) -> CollectionResult {
286- match pat {
287- Pat :: Or ( orpat) => {
288- for p in & orpat. cases {
289- if self . extract_variants_from_pat ( p) == CollectionResult :: Incomplete {
290- return CollectionResult :: Incomplete ;
291- }
292- }
293- CollectionResult :: Complete
294- }
295- Pat :: TupleStruct ( ts) => {
296- if let Some ( variant) = astkind_variant_from_path ( & ts. path ) {
297- self . node_types . insert ( variant) ;
298- CollectionResult :: Complete
299- } else {
300- CollectionResult :: Incomplete
301- }
302- }
303- _ => CollectionResult :: Incomplete ,
304- }
305- }
306- }
307-
30897/// Result of attempting to collect node type variants.
30998#[ derive( Debug , PartialEq , Eq ) ]
31099enum CollectionResult {
@@ -315,29 +104,6 @@ enum CollectionResult {
315104 Incomplete ,
316105}
317106
318- fn is_node_kind_call ( expr : & Expr ) -> bool {
319- if let Expr :: MethodCall ( mc) = expr
320- && mc. method == "kind"
321- && mc. args . is_empty ( )
322- && let Expr :: Path ( p) = & * mc. receiver
323- {
324- return p. path . is_ident ( "node" ) ;
325- }
326- false
327- }
328-
329- /// Extract AstKind variant from something like `AstKind::Variant`
330- fn astkind_variant_from_path ( path : & SynPath ) -> Option < String > {
331- // Expect `AstKind::Variant`
332- if path. segments . len ( ) != 2 {
333- return None ;
334- }
335- if path. segments [ 0 ] . ident != "AstKind" {
336- return None ;
337- }
338- Some ( path. segments [ 1 ] . ident . to_string ( ) )
339- }
340-
341107/// Format Rust code with `rustfmt`.
342108///
343109/// Does not format on disk - interfaces with `rustfmt` via stdin/stdout.
0 commit comments