@@ -126,13 +126,36 @@ where
126126 }
127127}
128128
129+ /// Represents how a class member is accessed within the code.
130+ /// Variants:
131+ ///
132+ /// - `Write`:
133+ /// The member is being assigned to or mutated.
134+ /// Example: `this.count = 10;`
135+ /// This indicates the member’s value/state changes at this point.
136+ ///
137+ /// - `MeaningfulRead`:
138+ /// The member’s value is retrieved and used in a way that affects program logic.
139+ /// Example: `if (this.enabled) { ... }` or `let x = this.value + 1;`
140+ /// These reads influence control flow or computation.
141+ ///
142+ /// - `TrivialRead`:
143+ /// The member is accessed, but its value is not used in a way that
144+ /// meaningfully affects logic.
145+ /// Example: `this.value;` as a standalone expression, or a read that is optimized away.
146+ /// This is mostly for distinguishing "dead reads" from truly meaningful ones.
147+ #[ derive( Debug , Clone , Hash , Eq , PartialEq ) ]
148+ enum AccessKind {
149+ Write ,
150+ MeaningfulRead ,
151+ TrivialRead ,
152+ }
153+
129154#[ derive( Debug , Clone , Hash , Eq , PartialEq ) ]
130155pub struct ClassMemberReference {
131156 pub name : Text ,
132157 pub range : TextRange ,
133- /// Indicates if the read is meaningful (e.g. used in an expression) or not (e.g. part of a destructuring assignment).
134- /// `None` if not applicable (e.g. for write references).
135- pub is_meaningful_read : Option < bool > ,
158+ pub access_kind : AccessKind ,
136159}
137160
138161#[ derive( Debug , Clone , Eq , PartialEq , Default ) ]
@@ -359,9 +382,9 @@ impl ThisScopeReferences {
359382 ClassMemberReference {
360383 name : id. to_trimmed_text ( ) . clone ( ) ,
361384 range : id. syntax ( ) . text_trimmed_range ( ) ,
362- is_meaningful_read : Some ( is_used_in_expression_context (
385+ access_kind : get_read_access_kind (
363386 & AnyCandidateForUsedInExpressionNode :: from ( id) ,
364- ) ) ,
387+ ) ,
365388 }
366389 } )
367390 } )
@@ -439,8 +462,7 @@ impl ThisPatternResolver {
439462 Self :: extract_this_member_reference (
440463 assignment. as_js_static_member_assignment ( ) ,
441464 scoped_this_references,
442- // It is a write reference, so not applicable for meaningful read
443- None ,
465+ AccessKind :: Write ,
444466 )
445467 } )
446468 }
@@ -456,8 +478,7 @@ impl ThisPatternResolver {
456478 Self :: extract_this_member_reference (
457479 assignment. as_js_static_member_assignment ( ) ,
458480 scoped_this_references,
459- // It is a write reference, so not applicable for meaningful read
460- None ,
481+ AccessKind :: Write ,
461482 )
462483 } )
463484 } else {
@@ -486,8 +507,7 @@ impl ThisPatternResolver {
486507 return Self :: extract_this_member_reference (
487508 rest_params. target ( ) . ok ( ) ?. as_js_static_member_assignment ( ) ,
488509 scoped_this_references,
489- // It is a write reference, so not applicable for meaningful read
490- None ,
510+ AccessKind :: Write ,
491511 ) ;
492512 }
493513 if let Some ( property) = prop
@@ -503,8 +523,7 @@ impl ThisPatternResolver {
503523 . as_any_js_assignment ( ) ?
504524 . as_js_static_member_assignment ( ) ,
505525 scoped_this_references,
506- // It is a write reference, so not applicable for meaningful read
507- None ,
526+ AccessKind :: Write ,
508527 ) ;
509528 }
510529 None
@@ -523,7 +542,7 @@ impl ThisPatternResolver {
523542 fn extract_this_member_reference (
524543 operand : Option < & JsStaticMemberAssignment > ,
525544 scoped_this_references : & [ FunctionThisReferences ] ,
526- is_meaningful_read : Option < bool > ,
545+ access_kind : AccessKind ,
527546 ) -> Option < ClassMemberReference > {
528547 operand. and_then ( |assignment| {
529548 if let Ok ( object) = assignment. object ( )
@@ -535,15 +554,15 @@ impl ThisPatternResolver {
535554 . map ( |name| ClassMemberReference {
536555 name : name. to_trimmed_text ( ) ,
537556 range : name. syntax ( ) . text_trimmed_range ( ) ,
538- is_meaningful_read ,
557+ access_kind : access_kind . clone ( ) ,
539558 } )
540559 . or_else ( || {
541560 member
542561 . as_js_private_name ( )
543562 . map ( |private_name| ClassMemberReference {
544563 name : private_name. to_trimmed_text ( ) ,
545564 range : private_name. syntax ( ) . text_trimmed_range ( ) ,
546- is_meaningful_read ,
565+ access_kind ,
547566 } )
548567 } )
549568 } )
@@ -637,9 +656,7 @@ fn handle_object_binding_pattern(
637656 reads. insert ( ClassMemberReference {
638657 name : declarator. clone ( ) . to_trimmed_text ( ) ,
639658 range : declarator. clone ( ) . syntax ( ) . text_trimmed_range ( ) ,
640- is_meaningful_read : Some ( is_used_in_expression_context (
641- & declarator. clone ( ) . into ( ) ,
642- ) ) ,
659+ access_kind : get_read_access_kind ( & declarator. clone ( ) . into ( ) ) ,
643660 } ) ;
644661 }
645662 }
@@ -675,7 +692,7 @@ fn handle_static_member_expression(
675692 reads. insert ( ClassMemberReference {
676693 name : member. to_trimmed_text ( ) ,
677694 range : member. syntax ( ) . text_trimmed_range ( ) ,
678- is_meaningful_read : Some ( is_used_in_expression_context ( & static_member. into ( ) ) ) ,
695+ access_kind : get_read_access_kind ( & static_member. into ( ) ) ,
679696 } ) ;
680697 }
681698}
@@ -721,8 +738,7 @@ fn handle_assignment_expression(
721738 && let Some ( name) = ThisPatternResolver :: extract_this_member_reference (
722739 operand. as_js_static_member_assignment ( ) ,
723740 scoped_this_references,
724- // Nodes inside assignment expressions are considered meaningful reads e.g. this.x += 1;
725- Some ( true ) ,
741+ AccessKind :: MeaningfulRead ,
726742 )
727743 {
728744 reads. insert ( name. clone ( ) ) ;
@@ -749,8 +765,7 @@ fn handle_assignment_expression(
749765 && let Some ( name) = ThisPatternResolver :: extract_this_member_reference (
750766 assignment. as_js_static_member_assignment ( ) ,
751767 scoped_this_references,
752- // It is a write reference, so not applicable for meaningful read
753- None ,
768+ AccessKind :: Write ,
754769 )
755770 {
756771 writes. insert ( name. clone ( ) ) ;
@@ -787,19 +802,15 @@ fn handle_pre_or_post_update_expression(
787802 && let Some ( name) = ThisPatternResolver :: extract_this_member_reference (
788803 operand. as_js_static_member_assignment ( ) ,
789804 scoped_this_references,
790- None ,
805+ AccessKind :: Write ,
791806 )
792807 {
793- writes. insert ( ClassMemberReference {
794- name : name. name . clone ( ) ,
795- range : name. range ,
796- is_meaningful_read : None ,
797- } ) ;
808+ writes. insert ( name. clone ( ) ) ;
798809 reads. insert ( ClassMemberReference {
799810 name : name. name ,
800811 range : name. range ,
801- is_meaningful_read : Some ( is_used_in_expression_context (
802- & AnyCandidateForUsedInExpressionNode :: from ( js_update_expression. clone ( ) ) ,
812+ access_kind : get_read_access_kind ( & AnyCandidateForUsedInExpressionNode :: from (
813+ js_update_expression. clone ( ) ,
803814 ) ) ,
804815 } ) ;
805816 }
@@ -842,8 +853,8 @@ fn collect_class_property_reads_from_static_member(
842853 reads. insert ( ClassMemberReference {
843854 name,
844855 range : static_member. syntax ( ) . text_trimmed_range ( ) ,
845- is_meaningful_read : Some ( is_used_in_expression_context (
846- & AnyCandidateForUsedInExpressionNode :: from ( static_member. clone ( ) ) ,
856+ access_kind : get_read_access_kind ( & AnyCandidateForUsedInExpressionNode :: from (
857+ static_member. clone ( ) ,
847858 ) ) ,
848859 } ) ;
849860 }
@@ -872,45 +883,45 @@ fn is_within_scope_without_shadowing(
872883 false
873884}
874885
886+ /// Determines the kind of read access for a given node.
887+ fn get_read_access_kind ( node : & AnyCandidateForUsedInExpressionNode ) -> AccessKind {
888+ if is_used_in_expression_context ( node) {
889+ AccessKind :: MeaningfulRead
890+ } else {
891+ AccessKind :: TrivialRead
892+ }
893+ }
894+
875895/// Checks if the given node is used in an expression context
876896/// (e.g., return, call arguments, conditionals, binary expressions).
877897/// Not limited to `this` references. Can be used for any node, but requires more work e.g.
878898/// Returns `true` if the read is meaningful, `false` otherwise.
879899fn is_used_in_expression_context ( node : & AnyCandidateForUsedInExpressionNode ) -> bool {
880- let mut current: JsSyntaxNode =
881- if let Some ( expression) = AnyJsExpression :: cast ( node. syntax ( ) . clone ( ) ) {
882- expression. syntax ( ) . clone ( ) // get JsSyntaxNode
883- } else {
884- node. syntax ( ) . clone ( ) // fallback to the node itself
885- } ;
886-
887- // Limit the number of parent traversals to avoid deep recursion
888- for _ in 0 ..8 {
889- if let Some ( parent) = current. parent ( ) {
890- match parent. kind ( ) {
891- JsSyntaxKind :: JS_RETURN_STATEMENT
892- | JsSyntaxKind :: JS_CALL_ARGUMENTS
893- | JsSyntaxKind :: JS_CONDITIONAL_EXPRESSION
894- | JsSyntaxKind :: JS_LOGICAL_EXPRESSION
895- | JsSyntaxKind :: JS_THROW_STATEMENT
896- | JsSyntaxKind :: JS_AWAIT_EXPRESSION
897- | JsSyntaxKind :: JS_YIELD_EXPRESSION
898- | JsSyntaxKind :: JS_UNARY_EXPRESSION
899- | JsSyntaxKind :: JS_TEMPLATE_EXPRESSION
900- | JsSyntaxKind :: JS_CALL_EXPRESSION // (callee)
901- | JsSyntaxKind :: JS_NEW_EXPRESSION
902- | JsSyntaxKind :: JS_IF_STATEMENT
903- | JsSyntaxKind :: JS_SWITCH_STATEMENT
904- | JsSyntaxKind :: JS_FOR_STATEMENT
905- | JsSyntaxKind :: JS_FOR_IN_STATEMENT
906- | JsSyntaxKind :: JS_FOR_OF_STATEMENT
907- | JsSyntaxKind :: JS_BINARY_EXPRESSION => return true ,
908- _ => current = parent,
909- }
910- } else {
911- break ;
900+ let mut current = node. syntax ( ) . clone ( ) ;
901+
902+ if let Some ( parent) = current. parent ( ) {
903+ match parent. kind ( ) {
904+ JsSyntaxKind :: JS_RETURN_STATEMENT
905+ | JsSyntaxKind :: JS_CALL_ARGUMENTS
906+ | JsSyntaxKind :: JS_CONDITIONAL_EXPRESSION
907+ | JsSyntaxKind :: JS_LOGICAL_EXPRESSION
908+ | JsSyntaxKind :: JS_THROW_STATEMENT
909+ | JsSyntaxKind :: JS_AWAIT_EXPRESSION
910+ | JsSyntaxKind :: JS_YIELD_EXPRESSION
911+ | JsSyntaxKind :: JS_UNARY_EXPRESSION
912+ | JsSyntaxKind :: JS_TEMPLATE_EXPRESSION
913+ | JsSyntaxKind :: JS_CALL_EXPRESSION // (callee)
914+ | JsSyntaxKind :: JS_NEW_EXPRESSION
915+ | JsSyntaxKind :: JS_IF_STATEMENT
916+ | JsSyntaxKind :: JS_SWITCH_STATEMENT
917+ | JsSyntaxKind :: JS_FOR_STATEMENT
918+ | JsSyntaxKind :: JS_FOR_IN_STATEMENT
919+ | JsSyntaxKind :: JS_FOR_OF_STATEMENT
920+ | JsSyntaxKind :: JS_BINARY_EXPRESSION => return true ,
921+ _ => current = parent,
912922 }
913923 }
924+
914925 false
915926}
916927
@@ -945,7 +956,7 @@ mod tests {
945956 } ) ;
946957
947958 assert_eq ! (
948- found. is_meaningful_read , * expected_meaningful,
959+ found. access_kind , * expected_meaningful,
949960 "Case '{}' failed: read '{}' meaningful mismatch" ,
950961 description, expected_name
951962 ) ;
@@ -969,7 +980,7 @@ mod tests {
969980 } ) ;
970981
971982 assert_eq ! (
972- found. is_meaningful_read , * expected_meaningful,
983+ found. access_kind , * expected_meaningful,
973984 "Case '{}' failed: write '{}' meaningful mismatch" ,
974985 description, expected_name
975986 ) ;
0 commit comments