1use oxc_ast::ast::*;
2
3use crate::{
4 ToBigInt, ToIntegerIndex, constant_evaluation::DetermineValueType, to_numeric::ToNumeric,
5 to_primitive::ToPrimitive,
6};
7
8use super::{MayHaveSideEffects, PropertyReadSideEffects, context::MayHaveSideEffectsContext};
9
10impl<'a> MayHaveSideEffects<'a> for Expression<'a> {
11 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
12 match self {
13 Expression::Identifier(ident) => ident.may_have_side_effects(ctx),
14 Expression::NumericLiteral(_)
15 | Expression::BooleanLiteral(_)
16 | Expression::StringLiteral(_)
17 | Expression::BigIntLiteral(_)
18 | Expression::NullLiteral(_)
19 | Expression::RegExpLiteral(_)
20 | Expression::MetaProperty(_)
21 | Expression::ThisExpression(_)
22 | Expression::ArrowFunctionExpression(_)
23 | Expression::FunctionExpression(_)
24 | Expression::Super(_) => false,
25 Expression::TemplateLiteral(e) => e.may_have_side_effects(ctx),
26 Expression::UnaryExpression(e) => e.may_have_side_effects(ctx),
27 Expression::LogicalExpression(e) => e.may_have_side_effects(ctx),
28 Expression::ParenthesizedExpression(e) => e.expression.may_have_side_effects(ctx),
29 Expression::ConditionalExpression(e) => {
30 if e.test.may_have_side_effects(ctx) {
31 return true;
32 }
33 if is_side_effect_free_unbound_identifier_ref(&e.alternate, &e.test, false, ctx) {
35 return e.consequent.may_have_side_effects(ctx);
36 }
37 if is_side_effect_free_unbound_identifier_ref(&e.consequent, &e.test, true, ctx) {
39 return e.alternate.may_have_side_effects(ctx);
40 }
41 e.consequent.may_have_side_effects(ctx) || e.alternate.may_have_side_effects(ctx)
42 }
43 Expression::SequenceExpression(e) => {
44 e.expressions.iter().any(|e| e.may_have_side_effects(ctx))
45 }
46 Expression::BinaryExpression(e) => e.may_have_side_effects(ctx),
47 Expression::ObjectExpression(object_expr) => {
48 object_expr.properties.iter().any(|property| property.may_have_side_effects(ctx))
49 }
50 Expression::ArrayExpression(e) => e.may_have_side_effects(ctx),
51 Expression::ClassExpression(e) => e.may_have_side_effects(ctx),
52 Expression::ChainExpression(e) => e.expression.may_have_side_effects(ctx),
54 match_member_expression!(Expression) => {
55 self.to_member_expression().may_have_side_effects(ctx)
56 }
57 Expression::CallExpression(e) => e.may_have_side_effects(ctx),
58 Expression::NewExpression(e) => e.may_have_side_effects(ctx),
59 Expression::TaggedTemplateExpression(e) => e.may_have_side_effects(ctx),
60 _ => true,
61 }
62 }
63}
64
65impl<'a> MayHaveSideEffects<'a> for IdentifierReference<'a> {
66 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
67 match self.name.as_str() {
68 "NaN" | "Infinity" | "undefined" => false,
69 _ => ctx.unknown_global_side_effects() && ctx.is_global_reference(self),
73 }
74 }
75}
76
77impl<'a> MayHaveSideEffects<'a> for TemplateLiteral<'a> {
78 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
79 self.expressions.iter().any(|e| {
80 e.to_primitive(ctx).is_symbol() != Some(false) || e.may_have_side_effects(ctx)
84 })
85 }
86}
87
88impl<'a> MayHaveSideEffects<'a> for UnaryExpression<'a> {
89 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
90 match self.operator {
91 UnaryOperator::Delete => true,
92 UnaryOperator::Void | UnaryOperator::LogicalNot => {
93 self.argument.may_have_side_effects(ctx)
94 }
95 UnaryOperator::Typeof => {
96 if matches!(&self.argument, Expression::Identifier(_)) {
97 false
98 } else {
99 self.argument.may_have_side_effects(ctx)
100 }
101 }
102 UnaryOperator::UnaryPlus => {
103 self.argument.to_primitive(ctx).is_symbol_or_bigint() != Some(false)
106 || self.argument.may_have_side_effects(ctx)
107 }
108 UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot => {
109 self.argument.to_primitive(ctx).is_symbol() != Some(false)
112 || self.argument.may_have_side_effects(ctx)
113 }
114 }
115 }
116}
117
118impl<'a> MayHaveSideEffects<'a> for BinaryExpression<'a> {
119 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
120 match self.operator {
121 BinaryOperator::Equality
122 | BinaryOperator::Inequality
123 | BinaryOperator::StrictEquality
124 | BinaryOperator::StrictInequality
125 | BinaryOperator::LessThan
126 | BinaryOperator::LessEqualThan
127 | BinaryOperator::GreaterThan
128 | BinaryOperator::GreaterEqualThan => {
129 self.left.may_have_side_effects(ctx) || self.right.may_have_side_effects(ctx)
130 }
131 BinaryOperator::Instanceof => {
132 if let Expression::Identifier(right_ident) = &self.right {
136 let name = right_ident.name.as_str();
137 if is_known_global_constructor(name)
140 && ctx.is_global_reference(right_ident)
141 && !self.left.value_type(ctx).is_undetermined()
142 {
143 return false;
144 }
145 }
146 true
148 }
149 BinaryOperator::In => {
150 true
152 }
153 BinaryOperator::Addition => {
154 let left = self.left.to_primitive(ctx);
155 let right = self.right.to_primitive(ctx);
156 if left.is_string() == Some(true) || right.is_string() == Some(true) {
157 let other_side = if left.is_string() == Some(true) { right } else { left };
159 return other_side.is_symbol() != Some(false)
161 || self.left.may_have_side_effects(ctx)
162 || self.right.may_have_side_effects(ctx);
163 }
164
165 let left_to_numeric_type = left.to_numeric(ctx);
166 let right_to_numeric_type = right.to_numeric(ctx);
167 if (left_to_numeric_type.is_number() && right_to_numeric_type.is_number())
168 || (left_to_numeric_type.is_bigint() && right_to_numeric_type.is_bigint())
169 {
170 self.left.may_have_side_effects(ctx) || self.right.may_have_side_effects(ctx)
171 } else {
172 true
173 }
174 }
175 BinaryOperator::Subtraction
176 | BinaryOperator::Multiplication
177 | BinaryOperator::Division
178 | BinaryOperator::Remainder
179 | BinaryOperator::ShiftLeft
180 | BinaryOperator::BitwiseOR
181 | BinaryOperator::ShiftRight
182 | BinaryOperator::BitwiseXOR
183 | BinaryOperator::BitwiseAnd
184 | BinaryOperator::Exponential
185 | BinaryOperator::ShiftRightZeroFill => {
186 let left_to_numeric_type = self.left.to_numeric(ctx);
187 let right_to_numeric_type = self.right.to_numeric(ctx);
188 if left_to_numeric_type.is_bigint() && right_to_numeric_type.is_bigint() {
189 if self.operator == BinaryOperator::ShiftRightZeroFill {
190 true
191 } else if matches!(
192 self.operator,
193 BinaryOperator::Exponential
194 | BinaryOperator::Division
195 | BinaryOperator::Remainder
196 ) {
197 if let Expression::BigIntLiteral(right) = &self.right {
198 match self.operator {
199 BinaryOperator::Exponential => {
200 right.is_negative() || self.left.may_have_side_effects(ctx)
201 }
202 BinaryOperator::Division | BinaryOperator::Remainder => {
203 right.is_zero() || self.left.may_have_side_effects(ctx)
204 }
205 _ => unreachable!(),
206 }
207 } else {
208 true
209 }
210 } else {
211 self.left.may_have_side_effects(ctx)
212 || self.right.may_have_side_effects(ctx)
213 }
214 } else if left_to_numeric_type.is_number() && right_to_numeric_type.is_number() {
215 self.left.may_have_side_effects(ctx) || self.right.may_have_side_effects(ctx)
216 } else {
217 true
218 }
219 }
220 }
221 }
222}
223
224fn is_pure_regexp(name: &str, args: &[Argument<'_>]) -> bool {
225 name == "RegExp"
226 && match args.len() {
227 0 | 1 => true,
228 2 => args[1].as_expression().is_some_and(|e| {
229 matches!(e, Expression::Identifier(_) | Expression::StringLiteral(_))
230 }),
231 _ => false,
232 }
233}
234
235#[rustfmt::skip]
236fn is_pure_global_function(name: &str) -> bool {
237 matches!(name, "decodeURI" | "decodeURIComponent" | "encodeURI" | "encodeURIComponent"
238 | "escape" | "isFinite" | "isNaN" | "parseFloat" | "parseInt")
239}
240
241#[rustfmt::skip]
242fn is_pure_call(name: &str) -> bool {
243 matches!(name, "Date" | "Boolean" | "Error" | "EvalError" | "RangeError" | "ReferenceError"
244 | "SyntaxError" | "TypeError" | "URIError" | "Number" | "Object" | "String" | "Symbol")
245}
246
247#[rustfmt::skip]
248fn is_pure_constructor(name: &str) -> bool {
249 matches!(name, "Set" | "Map" | "WeakSet" | "WeakMap" | "ArrayBuffer" | "Date"
250 | "Boolean" | "Error" | "EvalError" | "RangeError" | "ReferenceError"
251 | "SyntaxError" | "TypeError" | "URIError" | "Number" | "Object" | "String" | "Symbol")
252}
253
254fn is_known_global_constructor(name: &str) -> bool {
258 matches!(
260 name,
261 "AggregateError"
262 | "Array"
263 | "ArrayBuffer"
264 | "BigInt"
265 | "BigInt64Array"
266 | "BitUint64Array"
267 | "Boolean"
268 | "DataView"
269 | "Date"
270 | "Error"
271 | "EvalError"
272 | "FinalizationRegistry"
273 | "Float32Array"
274 | "Float64Array"
275 | "Function"
276 | "Int8Array"
277 | "Int16Array"
278 | "Int32Array"
279 | "Iterator"
280 | "Map"
281 | "Number"
282 | "Object"
283 | "Promise"
284 | "Proxy"
285 | "RangeError"
286 | "ReferenceError"
287 | "RegExp"
288 | "Set"
289 | "SharedArrayBuffer"
290 | "String"
291 | "Symbol"
292 | "SyntaxError"
293 | "TypeError"
294 | "Uint8Array"
295 | "Uint8ClampedArray"
296 | "Uint16Array"
297 | "Uint32Array"
298 | "URIError"
299 | "WeakMap"
300 | "WeakSet"
301 )
302}
303
304impl<'a> MayHaveSideEffects<'a> for LogicalExpression<'a> {
305 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
306 if self.left.may_have_side_effects(ctx) {
307 return true;
308 }
309 match self.operator {
310 LogicalOperator::And => {
311 if is_side_effect_free_unbound_identifier_ref(&self.right, &self.left, true, ctx) {
313 return false;
314 }
315 }
316 LogicalOperator::Or => {
317 if is_side_effect_free_unbound_identifier_ref(&self.right, &self.left, false, ctx) {
319 return false;
320 }
321 }
322 LogicalOperator::Coalesce => {}
323 }
324 self.right.may_have_side_effects(ctx)
325 }
326}
327
328impl<'a> MayHaveSideEffects<'a> for ArrayExpression<'a> {
329 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
330 self.elements.iter().any(|element| element.may_have_side_effects(ctx))
331 }
332}
333
334impl<'a> MayHaveSideEffects<'a> for ArrayExpressionElement<'a> {
335 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
336 match self {
337 ArrayExpressionElement::SpreadElement(e) => match &e.argument {
338 Expression::ArrayExpression(arr) => arr.may_have_side_effects(ctx),
339 Expression::StringLiteral(_) => false,
340 Expression::TemplateLiteral(t) => t.may_have_side_effects(ctx),
341 Expression::Identifier(ident) => {
342 !(ident.name == "arguments" && ctx.is_global_reference(ident))
344 }
345 _ => true,
346 },
347 match_expression!(ArrayExpressionElement) => {
348 self.to_expression().may_have_side_effects(ctx)
349 }
350 ArrayExpressionElement::Elision(_) => false,
351 }
352 }
353}
354
355impl<'a> MayHaveSideEffects<'a> for ObjectPropertyKind<'a> {
356 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
357 match self {
358 ObjectPropertyKind::ObjectProperty(o) => o.may_have_side_effects(ctx),
359 ObjectPropertyKind::SpreadProperty(e) => match &e.argument {
360 Expression::ArrayExpression(arr) => arr.may_have_side_effects(ctx),
361 Expression::StringLiteral(_) => false,
362 Expression::TemplateLiteral(t) => t.may_have_side_effects(ctx),
363 _ => true,
364 },
365 }
366 }
367}
368
369impl<'a> MayHaveSideEffects<'a> for ObjectProperty<'a> {
370 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
371 self.key.may_have_side_effects(ctx) || self.value.may_have_side_effects(ctx)
372 }
373}
374
375impl<'a> MayHaveSideEffects<'a> for PropertyKey<'a> {
376 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
377 match self {
378 PropertyKey::StaticIdentifier(_) | PropertyKey::PrivateIdentifier(_) => false,
379 match_expression!(PropertyKey) => {
380 self.to_expression().may_have_side_effects(ctx)
383 }
384 }
385 }
386}
387
388impl<'a> MayHaveSideEffects<'a> for Class<'a> {
389 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
391 if !self.decorators.is_empty() {
392 return true;
393 }
394
395 if self.super_class.as_ref().is_some_and(|sup| {
401 matches!(sup.without_parentheses(), Expression::ArrowFunctionExpression(_))
403 || sup.may_have_side_effects(ctx)
404 }) {
405 return true;
406 }
407
408 self.body.body.iter().any(|element| element.may_have_side_effects(ctx))
409 }
410}
411
412impl<'a> MayHaveSideEffects<'a> for ClassElement<'a> {
413 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
414 match self {
415 ClassElement::StaticBlock(block) => {
416 block.body.iter().any(|stmt| stmt.may_have_side_effects(ctx))
417 }
418 ClassElement::MethodDefinition(e) => {
419 !e.decorators.is_empty() || e.key.may_have_side_effects(ctx)
420 }
421 ClassElement::PropertyDefinition(e) => {
422 !e.decorators.is_empty()
423 || e.key.may_have_side_effects(ctx)
424 || (e.r#static
425 && e.value.as_ref().is_some_and(|v| v.may_have_side_effects(ctx)))
426 }
427 ClassElement::AccessorProperty(e) => {
428 !e.decorators.is_empty() || e.key.may_have_side_effects(ctx)
429 }
430 ClassElement::TSIndexSignature(_) => false,
431 }
432 }
433}
434
435impl<'a> MayHaveSideEffects<'a> for ChainElement<'a> {
436 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
437 match self {
438 ChainElement::CallExpression(e) => e.may_have_side_effects(ctx),
439 ChainElement::TSNonNullExpression(e) => e.expression.may_have_side_effects(ctx),
440 match_member_expression!(ChainElement) => {
441 self.to_member_expression().may_have_side_effects(ctx)
442 }
443 }
444 }
445}
446
447impl<'a> MayHaveSideEffects<'a> for MemberExpression<'a> {
448 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
449 match self {
450 MemberExpression::ComputedMemberExpression(e) => e.may_have_side_effects(ctx),
451 MemberExpression::StaticMemberExpression(e) => e.may_have_side_effects(ctx),
452 MemberExpression::PrivateFieldExpression(_) => {
453 ctx.property_read_side_effects() != PropertyReadSideEffects::None
454 }
455 }
456 }
457}
458
459impl<'a> MayHaveSideEffects<'a> for StaticMemberExpression<'a> {
460 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
461 property_access_may_have_side_effects(&self.object, &self.property.name, ctx)
462 }
463}
464
465impl<'a> MayHaveSideEffects<'a> for ComputedMemberExpression<'a> {
466 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
467 match &self.expression {
468 Expression::StringLiteral(s) => {
469 property_access_may_have_side_effects(&self.object, &s.value, ctx)
470 }
471 Expression::TemplateLiteral(t) => t.single_quasi().is_some_and(|quasi| {
472 property_access_may_have_side_effects(&self.object, &quasi, ctx)
473 }),
474 Expression::NumericLiteral(n) => !n.value.to_integer_index().is_some_and(|n| {
475 !integer_index_property_access_may_have_side_effects(&self.object, n, ctx)
476 }),
477 Expression::BigIntLiteral(b) => {
478 if b.is_negative() {
479 return true;
480 }
481 !b.to_big_int(ctx).and_then(ToIntegerIndex::to_integer_index).is_some_and(|b| {
482 !integer_index_property_access_may_have_side_effects(&self.object, b, ctx)
483 })
484 }
485 _ => true,
486 }
487 }
488}
489
490fn property_access_may_have_side_effects<'a>(
491 object: &Expression<'a>,
492 property: &str,
493 ctx: &impl MayHaveSideEffectsContext<'a>,
494) -> bool {
495 if object.may_have_side_effects(ctx) {
496 return true;
497 }
498 if ctx.property_read_side_effects() == PropertyReadSideEffects::None {
499 return false;
500 }
501
502 match property {
503 "length" => {
504 !(matches!(object, Expression::ArrayExpression(_))
505 || object.value_type(ctx).is_string())
506 }
507 _ => true,
508 }
509}
510
511fn integer_index_property_access_may_have_side_effects<'a>(
512 object: &Expression<'a>,
513 property: u32,
514 ctx: &impl MayHaveSideEffectsContext<'a>,
515) -> bool {
516 if object.may_have_side_effects(ctx) {
517 return true;
518 }
519 if ctx.property_read_side_effects() == PropertyReadSideEffects::None {
520 return false;
521 }
522 match object {
523 Expression::StringLiteral(s) => property as usize >= s.value.encode_utf16().count(),
524 Expression::ArrayExpression(arr) => property as usize >= get_array_minimum_length(arr),
525 _ => true,
526 }
527}
528
529fn get_array_minimum_length(arr: &ArrayExpression) -> usize {
530 arr.elements
531 .iter()
532 .map(|e| match e {
533 ArrayExpressionElement::SpreadElement(spread) => match &spread.argument {
534 Expression::ArrayExpression(arr) => get_array_minimum_length(arr),
535 Expression::StringLiteral(str) => str.value.chars().count(),
536 _ => 0,
537 },
538 _ => 1,
539 })
540 .sum()
541}
542
543impl<'a> MayHaveSideEffects<'a> for CallExpression<'a> {
545 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
546 if (self.pure && ctx.annotations()) || ctx.manual_pure_functions(&self.callee) {
547 return self.arguments.iter().any(|e| e.may_have_side_effects(ctx));
548 }
549
550 if let Expression::Identifier(ident) = &self.callee
551 && ctx.is_global_reference(ident)
552 && let name = ident.name.as_str()
553 && (is_pure_global_function(name)
554 || is_pure_call(name)
555 || is_pure_regexp(name, &self.arguments))
556 {
557 return self.arguments.iter().any(|e| e.may_have_side_effects(ctx));
558 }
559
560 let (object, name) = match &self.callee {
561 Expression::StaticMemberExpression(member) if !member.optional => {
562 (member.object.get_identifier_reference(), member.property.name.as_str())
563 }
564 Expression::ComputedMemberExpression(member) if !member.optional => {
565 match &member.expression {
566 Expression::StringLiteral(s) => {
567 (member.object.get_identifier_reference(), s.value.as_str())
568 }
569 _ => return true,
570 }
571 }
572 _ => return true,
573 };
574
575 let Some(object) = object else { return true };
576 if !ctx.is_global_reference(object) {
577 return true;
578 }
579
580 #[rustfmt::skip]
581 let is_global = match object.name.as_str() {
582 "Array" => matches!(name, "isArray" | "of"),
583 "ArrayBuffer" => name == "isView",
584 "Date" => matches!(name, "now" | "parse" | "UTC"),
585 "Math" => matches!(name, "abs" | "acos" | "acosh" | "asin" | "asinh" | "atan" | "atan2" | "atanh"
586 | "cbrt" | "ceil" | "clz32" | "cos" | "cosh" | "exp" | "expm1" | "floor" | "fround" | "hypot"
587 | "imul" | "log" | "log10" | "log1p" | "log2" | "max" | "min" | "pow" | "random" | "round"
588 | "sign" | "sin" | "sinh" | "sqrt" | "tan" | "tanh" | "trunc"),
589 "Number" => matches!(name, "isFinite" | "isInteger" | "isNaN" | "isSafeInteger" | "parseFloat" | "parseInt"),
590 "Object" => matches!(name, "create" | "getOwnPropertyDescriptor" | "getOwnPropertyDescriptors" | "getOwnPropertyNames"
591 | "getOwnPropertySymbols" | "getPrototypeOf" | "hasOwn" | "is" | "isExtensible" | "isFrozen" | "isSealed" | "keys"),
592 "String" => matches!(name, "fromCharCode" | "fromCodePoint" | "raw"),
593 "Symbol" => matches!(name, "for" | "keyFor"),
594 "URL" => name == "canParse",
595 "Float32Array" | "Float64Array" | "Int16Array" | "Int32Array" | "Int8Array" | "Uint16Array" | "Uint32Array" | "Uint8Array" | "Uint8ClampedArray" => name == "of",
596 _ => false,
597 };
598
599 if is_global {
600 return self.arguments.iter().any(|e| e.may_have_side_effects(ctx));
601 }
602
603 true
604 }
605}
606
607impl<'a> MayHaveSideEffects<'a> for NewExpression<'a> {
609 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
610 if (self.pure && ctx.annotations()) || ctx.manual_pure_functions(&self.callee) {
611 return self.arguments.iter().any(|e| e.may_have_side_effects(ctx));
612 }
613 if let Expression::Identifier(ident) = &self.callee
614 && ctx.is_global_reference(ident)
615 && let name = ident.name.as_str()
616 && (is_pure_constructor(name) || is_pure_regexp(name, &self.arguments))
617 {
618 return self.arguments.iter().any(|e| e.may_have_side_effects(ctx));
619 }
620 true
621 }
622}
623
624impl<'a> MayHaveSideEffects<'a> for TaggedTemplateExpression<'a> {
625 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
626 if ctx.manual_pure_functions(&self.tag) {
627 self.quasi.may_have_side_effects(ctx)
628 } else {
629 true
630 }
631 }
632}
633
634impl<'a> MayHaveSideEffects<'a> for Argument<'a> {
635 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
636 match self {
637 Argument::SpreadElement(e) => match &e.argument {
638 Expression::ArrayExpression(arr) => arr.may_have_side_effects(ctx),
639 Expression::StringLiteral(_) => false,
640 Expression::TemplateLiteral(t) => t.may_have_side_effects(ctx),
641 _ => true,
642 },
643 match_expression!(Argument) => self.to_expression().may_have_side_effects(ctx),
644 }
645 }
646}
647
648impl<'a> MayHaveSideEffects<'a> for AssignmentTarget<'a> {
649 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
652 match self {
653 match_simple_assignment_target!(AssignmentTarget) => {
654 self.to_simple_assignment_target().may_have_side_effects(ctx)
655 }
656 match_assignment_target_pattern!(AssignmentTarget) => true,
657 }
658 }
659}
660
661impl<'a> MayHaveSideEffects<'a> for SimpleAssignmentTarget<'a> {
662 fn may_have_side_effects(&self, ctx: &impl MayHaveSideEffectsContext<'a>) -> bool {
663 match self {
664 SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => false,
665 SimpleAssignmentTarget::StaticMemberExpression(member_expr) => {
666 member_expr.object.may_have_side_effects(ctx)
667 }
668 SimpleAssignmentTarget::ComputedMemberExpression(member_expr) => {
669 member_expr.object.may_have_side_effects(ctx)
670 || member_expr.expression.may_have_side_effects(ctx)
671 }
672 SimpleAssignmentTarget::PrivateFieldExpression(member_expr) => {
673 member_expr.object.may_have_side_effects(ctx)
674 }
675 SimpleAssignmentTarget::TSAsExpression(_)
676 | SimpleAssignmentTarget::TSNonNullExpression(_)
677 | SimpleAssignmentTarget::TSSatisfiesExpression(_)
678 | SimpleAssignmentTarget::TSTypeAssertion(_) => true,
679 }
680 }
681}
682
683fn is_side_effect_free_unbound_identifier_ref<'a>(
692 value: &Expression<'a>,
693 guard_condition: &Expression<'a>,
694 mut is_yes_branch: bool,
695 ctx: &impl MayHaveSideEffectsContext<'a>,
696) -> bool {
697 let Some(ident) = value.get_identifier_reference() else {
698 return false;
699 };
700 if !ctx.is_global_reference(ident) {
701 return false;
702 }
703
704 let Expression::BinaryExpression(bin_expr) = guard_condition else {
705 return false;
706 };
707 match bin_expr.operator {
708 BinaryOperator::StrictEquality
709 | BinaryOperator::StrictInequality
710 | BinaryOperator::Equality
711 | BinaryOperator::Inequality => {
712 let (mut ty_of, mut string) = (&bin_expr.left, &bin_expr.right);
713 if matches!(ty_of, Expression::StringLiteral(_)) {
714 std::mem::swap(&mut string, &mut ty_of);
715 }
716
717 let Expression::UnaryExpression(unary) = ty_of else {
718 return false;
719 };
720 if !(unary.operator == UnaryOperator::Typeof
721 && matches!(unary.argument, Expression::Identifier(_)))
722 {
723 return false;
724 }
725
726 let Expression::StringLiteral(string) = string else {
727 return false;
728 };
729
730 let is_undefined_check = string.value == "undefined";
731 if (is_undefined_check == is_yes_branch)
732 == matches!(
733 bin_expr.operator,
734 BinaryOperator::Inequality | BinaryOperator::StrictInequality
735 )
736 && unary.argument.is_specific_id(&ident.name)
737 {
738 return true;
739 }
740 }
741 BinaryOperator::LessThan
742 | BinaryOperator::LessEqualThan
743 | BinaryOperator::GreaterThan
744 | BinaryOperator::GreaterEqualThan => {
745 let (mut ty_of, mut string) = (&bin_expr.left, &bin_expr.right);
746 if matches!(ty_of, Expression::StringLiteral(_)) {
747 std::mem::swap(&mut string, &mut ty_of);
748 is_yes_branch = !is_yes_branch;
749 }
750
751 let Expression::UnaryExpression(unary) = ty_of else {
752 return false;
753 };
754 if !(unary.operator == UnaryOperator::Typeof
755 && matches!(unary.argument, Expression::Identifier(_)))
756 {
757 return false;
758 }
759
760 let Expression::StringLiteral(string) = string else {
761 return false;
762 };
763 if string.value != "u" {
764 return false;
765 }
766
767 if is_yes_branch
768 == matches!(
769 bin_expr.operator,
770 BinaryOperator::LessThan | BinaryOperator::LessEqualThan
771 )
772 && unary.argument.is_specific_id(&ident.name)
773 {
774 return true;
775 }
776 }
777 _ => {}
778 }
779
780 false
781}