@@ -3,7 +3,7 @@ use syn::{Expr, Pat, Stmt};
33use crate :: {
44 CollectionResult ,
55 node_type_set:: NodeTypeSet ,
6- utils:: { astkind_variant_from_path, is_node_kind_call} ,
6+ utils:: { astkind_variant_from_path, find_impl_function , is_node_kind_call} ,
77} ;
88
99/// Detects top-level `let AstKind::... = node.kind() else { return; }` patterns in the `run` method.
@@ -42,9 +42,9 @@ impl LetElseDetector {
4242 if is_node_kind_as_call ( & init. expr ) {
4343 // If the initializer is `node.kind().as_<variant>()`, extract that variant.
4444 if let Expr :: MethodCall ( mc) = & * init. expr
45- && let Some ( variant ) = extract_variant_from_as_call ( mc)
45+ && let Some ( variants ) = extract_variants_from_as_call ( mc)
4646 {
47- detector. node_types . insert ( variant ) ;
47+ detector. node_types . extend ( variants ) ;
4848 }
4949 } else {
5050 // Otherwise, the initializer is `node.kind()`, so extract from the pattern.
@@ -88,7 +88,7 @@ pub fn is_node_kind_as_call(expr: &Expr) -> bool {
8888 false
8989}
9090
91- fn extract_variant_from_as_call ( mc : & syn:: ExprMethodCall ) -> Option < String > {
91+ fn extract_variants_from_as_call ( mc : & syn:: ExprMethodCall ) -> Option < NodeTypeSet > {
9292 // Looking for `node.kind().as_<snake_case_variant>()`
9393 let method_ident = mc. method . to_string ( ) ;
9494 if !method_ident. starts_with ( "as_" ) || !mc. args . is_empty ( ) {
@@ -100,9 +100,55 @@ fn extract_variant_from_as_call(mc: &syn::ExprMethodCall) -> Option<String> {
100100 }
101101 let snake_variant = & method_ident[ 3 ..] ; // strip `as_`
102102 if snake_variant == "member_expression_kind" {
103- return None ;
103+ return get_member_expression_kinds ( ) ;
104+ }
105+ let mut node_type_set = NodeTypeSet :: new ( ) ;
106+ node_type_set. insert ( snake_to_pascal_case ( snake_variant) ) ;
107+ Some ( node_type_set)
108+ }
109+
110+ /// Fetches the current list of variants that can be returned by `AstKind::as_member_expression_kind()`.
111+ /// We read the source file to avoid hardcoding the list here and ensure this will stay updated.
112+ fn get_member_expression_kinds ( ) -> Option < NodeTypeSet > {
113+ // Read crates/oxc_ast/src/ast_kind_impl.rs and extract all variants in `as_member_expression_kind` function
114+ let ast_kind_impl_path = std:: path:: Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) )
115+ . parent ( ) ?
116+ . parent ( ) ?
117+ . join ( "crates" )
118+ . join ( "oxc_ast" )
119+ . join ( "src" )
120+ . join ( "ast_kind_impl.rs" ) ;
121+ let content = std:: fs:: read_to_string ( ast_kind_impl_path) . ok ( ) ?;
122+ let syntax = syn:: parse_file ( & content) . ok ( ) ?;
123+ let mut node_type_set = NodeTypeSet :: new ( ) ;
124+ for item in syntax. items {
125+ if let syn:: Item :: Impl ( impl_block) = item
126+ && let syn:: Type :: Path ( type_path) = impl_block. self_ty . as_ref ( )
127+ && type_path. path . segments . last ( ) ?. ident == "AstKind"
128+ {
129+ let impl_fn = find_impl_function ( & impl_block, "as_member_expression_kind" ) ;
130+ if let Some ( impl_fn) = impl_fn {
131+ // Look for `match self { ... }` inside the function body
132+ if impl_fn. block . stmts . len ( ) != 1 {
133+ return None ;
134+ }
135+ let stmt = & impl_fn. block . stmts [ 0 ] ;
136+ if let Stmt :: Expr ( Expr :: Match ( match_expr) , _) = stmt {
137+ for arm in & match_expr. arms {
138+ if let Pat :: TupleStruct ( ts) = & arm. pat
139+ && let Some ( variant) = self_astkind_variant_from_path ( & ts. path )
140+ {
141+ node_type_set. insert ( variant) ;
142+ }
143+ }
144+ if !node_type_set. is_empty ( ) {
145+ return Some ( node_type_set) ;
146+ }
147+ }
148+ }
149+ }
104150 }
105- Some ( snake_to_pascal_case ( snake_variant ) )
151+ None
106152}
107153
108154fn snake_to_pascal_case ( s : & str ) -> String {
@@ -117,3 +163,14 @@ fn snake_to_pascal_case(s: &str) -> String {
117163 } )
118164 . collect ( )
119165}
166+
167+ pub fn self_astkind_variant_from_path ( path : & syn:: Path ) -> Option < String > {
168+ // Expect `Self::Variant`
169+ if path. segments . len ( ) != 2 {
170+ return None ;
171+ }
172+ if path. segments [ 0 ] . ident != "Self" {
173+ return None ;
174+ }
175+ Some ( path. segments [ 1 ] . ident . to_string ( ) )
176+ }
0 commit comments