@@ -15,7 +15,7 @@ use oxc_cfg::{
1515} ;
1616use oxc_diagnostics:: OxcDiagnostic ;
1717use oxc_span:: { Atom , CompactStr , SourceType , Span } ;
18- use oxc_syntax:: { module_record:: ModuleRecord , operator :: AssignmentOperator } ;
18+ use oxc_syntax:: module_record:: ModuleRecord ;
1919
2020use crate :: {
2121 binder:: Binder ,
@@ -890,8 +890,17 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
890890
891891 let kind = AstKind :: AssignmentExpression ( self . alloc ( expr) ) ;
892892 self . enter_node ( kind) ;
893+
894+ if !expr. operator . is_assign ( ) {
895+ // Only when the operator is not an `=` operator, the left-hand side is both read and write.
896+ // <https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation>
897+ self . current_reference_flags = ReferenceFlags :: read_write ( ) ;
898+ }
899+
893900 self . visit_assignment_target ( & expr. left ) ;
894901
902+ self . current_reference_flags = ReferenceFlags :: empty ( ) ;
903+
895904 /* cfg */
896905 let cfg_ixs = control_flow ! ( self , |cfg| {
897906 if expr. operator. is_logical( ) {
@@ -1760,6 +1769,86 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
17601769 self . leave_node ( kind) ;
17611770 self . leave_scope ( ) ;
17621771 }
1772+
1773+ fn visit_update_expression ( & mut self , it : & UpdateExpression < ' a > ) {
1774+ let kind = AstKind :: UpdateExpression ( self . alloc ( it) ) ;
1775+ self . enter_node ( kind) ;
1776+ // `++a` or `a--`
1777+ // ^ ^ We always treat `a` as Read and Write reference,
1778+ self . current_reference_flags = ReferenceFlags :: read_write ( ) ;
1779+ self . visit_simple_assignment_target ( & it. argument ) ;
1780+ self . current_reference_flags = ReferenceFlags :: empty ( ) ;
1781+ self . leave_node ( kind) ;
1782+ }
1783+
1784+ fn visit_member_expression ( & mut self , it : & MemberExpression < ' a > ) {
1785+ let kind = AstKind :: MemberExpression ( self . alloc ( it) ) ;
1786+ self . enter_node ( kind) ;
1787+
1788+ // A.B = 1;
1789+ // ^^^ Can't treat A as a Write reference since it's A's property(B) that changes.
1790+ self . current_reference_flags -= ReferenceFlags :: Write ;
1791+
1792+ match it {
1793+ MemberExpression :: ComputedMemberExpression ( it) => {
1794+ self . visit_computed_member_expression ( it) ;
1795+ }
1796+ MemberExpression :: StaticMemberExpression ( it) => self . visit_static_member_expression ( it) ,
1797+ MemberExpression :: PrivateFieldExpression ( it) => self . visit_private_field_expression ( it) ,
1798+ }
1799+ self . leave_node ( kind) ;
1800+ }
1801+
1802+ fn visit_simple_assignment_target ( & mut self , it : & SimpleAssignmentTarget < ' a > ) {
1803+ let kind = AstKind :: SimpleAssignmentTarget ( self . alloc ( it) ) ;
1804+ self . enter_node ( kind) ;
1805+ let prev_reference_flags = self . current_reference_flags ;
1806+ // Except that the read-write flags has been set in visit_assignment_expression
1807+ // and visit_update_expression, this is always a write-only reference here.
1808+ if !self . current_reference_flags . is_write ( ) {
1809+ self . current_reference_flags = ReferenceFlags :: Write ;
1810+ }
1811+
1812+ match it {
1813+ SimpleAssignmentTarget :: AssignmentTargetIdentifier ( it) => {
1814+ self . visit_identifier_reference ( it) ;
1815+ }
1816+ SimpleAssignmentTarget :: TSAsExpression ( it) => {
1817+ self . visit_ts_as_expression ( it) ;
1818+ }
1819+ SimpleAssignmentTarget :: TSSatisfiesExpression ( it) => {
1820+ self . visit_ts_satisfies_expression ( it) ;
1821+ }
1822+ SimpleAssignmentTarget :: TSNonNullExpression ( it) => {
1823+ self . visit_ts_non_null_expression ( it) ;
1824+ }
1825+ SimpleAssignmentTarget :: TSTypeAssertion ( it) => {
1826+ self . visit_ts_type_assertion ( it) ;
1827+ }
1828+ SimpleAssignmentTarget :: TSInstantiationExpression ( it) => {
1829+ self . visit_ts_instantiation_expression ( it) ;
1830+ }
1831+ match_member_expression ! ( SimpleAssignmentTarget ) => {
1832+ self . visit_member_expression ( it. to_member_expression ( ) ) ;
1833+ }
1834+ }
1835+ self . current_reference_flags = prev_reference_flags;
1836+ self . leave_node ( kind) ;
1837+ }
1838+
1839+ fn visit_assignment_target_property_identifier (
1840+ & mut self ,
1841+ it : & AssignmentTargetPropertyIdentifier < ' a > ,
1842+ ) {
1843+ // NOTE: AstKind doesn't exists!
1844+ let prev_reference_flags = self . current_reference_flags ;
1845+ self . current_reference_flags = ReferenceFlags :: Write ;
1846+ self . visit_identifier_reference ( & it. binding ) ;
1847+ self . current_reference_flags = prev_reference_flags;
1848+ if let Some ( init) = & it. init {
1849+ self . visit_expression ( init) ;
1850+ }
1851+ }
17631852}
17641853
17651854impl < ' a > SemanticBuilder < ' a > {
@@ -1940,29 +2029,6 @@ impl<'a> SemanticBuilder<'a> {
19402029 AstKind :: IdentifierReference ( ident) => {
19412030 self . reference_identifier ( ident) ;
19422031 }
1943- AstKind :: UpdateExpression ( _) => {
1944- if !self . current_reference_flags . is_type ( )
1945- && self . is_not_expression_statement_parent ( )
1946- {
1947- self . current_reference_flags |= ReferenceFlags :: Read ;
1948- }
1949- self . current_reference_flags |= ReferenceFlags :: Write ;
1950- }
1951- AstKind :: AssignmentExpression ( expr) => {
1952- if expr. operator != AssignmentOperator :: Assign
1953- || self . is_not_expression_statement_parent ( )
1954- {
1955- self . current_reference_flags |= ReferenceFlags :: Read ;
1956- }
1957- }
1958- AstKind :: MemberExpression ( _) => {
1959- // A.B = 1;
1960- // ^^^ we can't treat A as Write reference, because it's the property(B) of A that change
1961- self . current_reference_flags -= ReferenceFlags :: Write ;
1962- }
1963- AstKind :: AssignmentTarget ( _) => {
1964- self . current_reference_flags |= ReferenceFlags :: Write ;
1965- }
19662032 AstKind :: LabeledStatement ( stmt) => {
19672033 self . unused_labels . add ( stmt. label . name . as_str ( ) ) ;
19682034 }
@@ -2018,27 +2084,12 @@ impl<'a> SemanticBuilder<'a> {
20182084 AstKind :: TSTypeName ( _) => {
20192085 self . current_reference_flags -= ReferenceFlags :: Type ;
20202086 }
2021- AstKind :: UpdateExpression ( _) => {
2022- if self . is_not_expression_statement_parent ( ) {
2023- self . current_reference_flags -= ReferenceFlags :: Read ;
2024- }
2025- self . current_reference_flags -= ReferenceFlags :: Write ;
2026- }
2027- AstKind :: AssignmentExpression ( _) | AstKind :: ExportNamedDeclaration ( _)
2087+ AstKind :: ExportNamedDeclaration ( _)
20282088 | AstKind :: TSTypeQuery ( _)
20292089 // Clear the reference flags that are set in AstKind::PropertySignature
20302090 | AstKind :: PropertyKey ( _) => {
20312091 self . current_reference_flags = ReferenceFlags :: empty ( ) ;
20322092 }
2033- AstKind :: AssignmentTarget ( _) =>{
2034- // Handle nested assignment targets like `({a: b} = obj)`
2035- if !matches ! (
2036- self . nodes. parent_kind( self . current_node_id) ,
2037- Some ( AstKind :: ObjectAssignmentTarget ( _) | AstKind :: ArrayAssignmentTarget ( _) )
2038- ) {
2039- self . current_reference_flags -= ReferenceFlags :: Write ;
2040- }
2041- } ,
20422093 AstKind :: LabeledStatement ( _) => self . unused_labels . mark_unused ( self . current_node_id ) ,
20432094 _ => { }
20442095 }
@@ -2066,34 +2117,12 @@ impl<'a> SemanticBuilder<'a> {
20662117 }
20672118
20682119 /// Resolve reference flags for the current ast node.
2120+ #[ inline]
20692121 fn resolve_reference_usages ( & self ) -> ReferenceFlags {
20702122 if self . current_reference_flags . is_empty ( ) {
20712123 ReferenceFlags :: Read
20722124 } else {
20732125 self . current_reference_flags
20742126 }
20752127 }
2076-
2077- fn is_not_expression_statement_parent ( & self ) -> bool {
2078- for node in self . nodes . ancestors ( self . current_node_id ) . skip ( 1 ) {
2079- return match node. kind ( ) {
2080- AstKind :: ParenthesizedExpression ( _) => continue ,
2081- AstKind :: ExpressionStatement ( _) => {
2082- if self . current_scope_flags ( ) . is_arrow ( ) {
2083- if let Some ( node) = self . nodes . ancestors ( node. id ( ) ) . nth ( 2 ) {
2084- // (x) => x++
2085- // ^^^ implicit return, we need to treat `x` as a read reference
2086- if matches ! ( node. kind( ) , AstKind :: ArrowFunctionExpression ( arrow) if arrow. expression)
2087- {
2088- return true ;
2089- }
2090- }
2091- }
2092- false
2093- }
2094- _ => true ,
2095- } ;
2096- }
2097- false
2098- }
20992128}
0 commit comments