@@ -15,7 +15,7 @@ use oxc_cfg::{
15
15
} ;
16
16
use oxc_diagnostics:: OxcDiagnostic ;
17
17
use oxc_span:: { Atom , CompactStr , SourceType , Span } ;
18
- use oxc_syntax:: { module_record:: ModuleRecord , operator :: AssignmentOperator } ;
18
+ use oxc_syntax:: module_record:: ModuleRecord ;
19
19
20
20
use crate :: {
21
21
binder:: Binder ,
@@ -890,8 +890,17 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
890
890
891
891
let kind = AstKind :: AssignmentExpression ( self . alloc ( expr) ) ;
892
892
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
+
893
900
self . visit_assignment_target ( & expr. left ) ;
894
901
902
+ self . current_reference_flags = ReferenceFlags :: empty ( ) ;
903
+
895
904
/* cfg */
896
905
let cfg_ixs = control_flow ! ( self , |cfg| {
897
906
if expr. operator. is_logical( ) {
@@ -1760,6 +1769,86 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
1760
1769
self . leave_node ( kind) ;
1761
1770
self . leave_scope ( ) ;
1762
1771
}
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
+ }
1763
1852
}
1764
1853
1765
1854
impl < ' a > SemanticBuilder < ' a > {
@@ -1940,29 +2029,6 @@ impl<'a> SemanticBuilder<'a> {
1940
2029
AstKind :: IdentifierReference ( ident) => {
1941
2030
self . reference_identifier ( ident) ;
1942
2031
}
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
- }
1966
2032
AstKind :: LabeledStatement ( stmt) => {
1967
2033
self . unused_labels . add ( stmt. label . name . as_str ( ) ) ;
1968
2034
}
@@ -2018,27 +2084,12 @@ impl<'a> SemanticBuilder<'a> {
2018
2084
AstKind :: TSTypeName ( _) => {
2019
2085
self . current_reference_flags -= ReferenceFlags :: Type ;
2020
2086
}
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 ( _)
2028
2088
| AstKind :: TSTypeQuery ( _)
2029
2089
// Clear the reference flags that are set in AstKind::PropertySignature
2030
2090
| AstKind :: PropertyKey ( _) => {
2031
2091
self . current_reference_flags = ReferenceFlags :: empty ( ) ;
2032
2092
}
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
- } ,
2042
2093
AstKind :: LabeledStatement ( _) => self . unused_labels . mark_unused ( self . current_node_id ) ,
2043
2094
_ => { }
2044
2095
}
@@ -2066,34 +2117,12 @@ impl<'a> SemanticBuilder<'a> {
2066
2117
}
2067
2118
2068
2119
/// Resolve reference flags for the current ast node.
2120
+ #[ inline]
2069
2121
fn resolve_reference_usages ( & self ) -> ReferenceFlags {
2070
2122
if self . current_reference_flags . is_empty ( ) {
2071
2123
ReferenceFlags :: Read
2072
2124
} else {
2073
2125
self . current_reference_flags
2074
2126
}
2075
2127
}
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
- }
2099
2128
}
0 commit comments