mirror of https://github.com/boa-dev/boa.git
Browse Source
This should hopefully improve our compilation times, both from a clean build and from an incremental compilation snapshot. Next would be the parser, but it imports `Context`, so it'll require a bit more work. The number of file changes is obviously big, but almost nothing was changed, I just moved everything to another crate and readjusted the imports of the `parser` module. (Though, I did have to change some details, because there were some functions on the ast that returned `ParseError`s, and the tests had to be moved to the parser)pull/2403/head
José Julián Espina
2 years ago
184 changed files with 4254 additions and 4448 deletions
@ -0,0 +1,22 @@
|
||||
[package] |
||||
name = "boa_ast" |
||||
description = "Abstract Syntax Tree definition for the Boa JavaScript engine." |
||||
keywords = ["javascript", "js", "syntax", "ast"] |
||||
categories = ["parser-implementations", "compilers"] |
||||
version.workspace = true |
||||
edition.workspace = true |
||||
authors.workspace = true |
||||
license.workspace = true |
||||
repository.workspace = true |
||||
rust-version.workspace = true |
||||
|
||||
[features] |
||||
serde = ["boa_interner/serde", "dep:serde"] |
||||
|
||||
[dependencies] |
||||
boa_interner.workspace = true |
||||
boa_macros.workspace = true |
||||
rustc-hash = "1.1.0" |
||||
serde = { version = "1.0.147", features = ["derive"], optional = true } |
||||
bitflags = "1.3.2" |
||||
num-bigint = "0.4.3" |
@ -0,0 +1,236 @@
|
||||
//! Array declaration Expression.
|
||||
|
||||
use crate::expression::operator::assign::AssignTarget; |
||||
use crate::pattern::{ArrayPattern, ArrayPatternElement, Pattern}; |
||||
use crate::try_break; |
||||
use crate::visitor::{VisitWith, Visitor, VisitorMut}; |
||||
use crate::{expression::Expression, ContainsSymbol}; |
||||
use boa_interner::{Interner, Sym, ToInternedString}; |
||||
use core::ops::ControlFlow; |
||||
|
||||
/// An array is an ordered collection of data (either primitive or object depending upon the
|
||||
/// language).
|
||||
///
|
||||
/// Arrays are used to store multiple values in a single variable.
|
||||
/// This is compared to a variable that can store only one value.
|
||||
///
|
||||
/// Each item in an array has a number attached to it, called a numeric index, that allows you
|
||||
/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
|
||||
/// methods.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct ArrayLiteral { |
||||
arr: Box<[Option<Expression>]>, |
||||
has_trailing_comma_spread: bool, |
||||
} |
||||
|
||||
impl ArrayLiteral { |
||||
/// Creates a new array literal.
|
||||
pub fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self |
||||
where |
||||
A: Into<Box<[Option<Expression>]>>, |
||||
{ |
||||
Self { |
||||
arr: array.into(), |
||||
has_trailing_comma_spread, |
||||
} |
||||
} |
||||
|
||||
/// Indicates if a spread operator in the array literal has a trailing comma.
|
||||
/// This is a syntax error in some cases.
|
||||
#[must_use] |
||||
pub fn has_trailing_comma_spread(&self) -> bool { |
||||
self.has_trailing_comma_spread |
||||
} |
||||
|
||||
/// Converts this `ArrayLiteral` into an [`ArrayPattern`].
|
||||
#[must_use] |
||||
pub fn to_pattern(&self, strict: bool) -> Option<ArrayPattern> { |
||||
if self.has_trailing_comma_spread() { |
||||
return None; |
||||
} |
||||
|
||||
let mut bindings = Vec::new(); |
||||
for (i, expr) in self.arr.iter().enumerate() { |
||||
let expr = if let Some(expr) = expr { |
||||
expr |
||||
} else { |
||||
bindings.push(ArrayPatternElement::Elision); |
||||
continue; |
||||
}; |
||||
match expr { |
||||
Expression::Identifier(ident) => { |
||||
if strict && *ident == Sym::ARGUMENTS { |
||||
return None; |
||||
} |
||||
|
||||
bindings.push(ArrayPatternElement::SingleName { |
||||
ident: *ident, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::Spread(spread) => { |
||||
match spread.target() { |
||||
Expression::Identifier(ident) => { |
||||
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident }); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccessRest { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
Expression::ArrayLiteral(array) => { |
||||
let pattern = array.to_pattern(strict)?.into(); |
||||
bindings.push(ArrayPatternElement::PatternRest { pattern }); |
||||
} |
||||
Expression::ObjectLiteral(object) => { |
||||
let pattern = object.to_pattern(strict)?.into(); |
||||
bindings.push(ArrayPatternElement::PatternRest { pattern }); |
||||
} |
||||
_ => return None, |
||||
} |
||||
if i + 1 != self.arr.len() { |
||||
return None; |
||||
} |
||||
} |
||||
Expression::Assign(assign) => match assign.lhs() { |
||||
AssignTarget::Identifier(ident) => { |
||||
bindings.push(ArrayPatternElement::SingleName { |
||||
ident: *ident, |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
AssignTarget::Access(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccess { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
AssignTarget::Pattern(pattern) => match pattern { |
||||
Pattern::Object(pattern) => { |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern: Pattern::Object(pattern.clone()), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
Pattern::Array(pattern) => { |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern: Pattern::Array(pattern.clone()), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
}, |
||||
}, |
||||
Expression::ArrayLiteral(array) => { |
||||
let pattern = array.to_pattern(strict)?.into(); |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::ObjectLiteral(object) => { |
||||
let pattern = object.to_pattern(strict)?.into(); |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccess { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
_ => return None, |
||||
} |
||||
} |
||||
Some(ArrayPattern::new(bindings.into())) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
self.arr |
||||
.iter() |
||||
.flatten() |
||||
.any(Expression::contains_arguments) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
self.arr.iter().flatten().any(|expr| expr.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl AsRef<[Option<Expression>]> for ArrayLiteral { |
||||
#[inline] |
||||
fn as_ref(&self) -> &[Option<Expression>] { |
||||
&self.arr |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for ArrayLiteral |
||||
where |
||||
T: Into<Box<[Option<Expression>]>>, |
||||
{ |
||||
#[inline] |
||||
fn from(decl: T) -> Self { |
||||
Self { |
||||
arr: decl.into(), |
||||
has_trailing_comma_spread: false, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for ArrayLiteral { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
let mut buf = String::from("["); |
||||
let mut first = true; |
||||
for e in &*self.arr { |
||||
if first { |
||||
first = false; |
||||
} else { |
||||
buf.push_str(", "); |
||||
} |
||||
if let Some(e) = e { |
||||
buf.push_str(&e.to_interned_string(interner)); |
||||
} |
||||
} |
||||
buf.push(']'); |
||||
buf |
||||
} |
||||
} |
||||
|
||||
impl From<ArrayLiteral> for Expression { |
||||
#[inline] |
||||
fn from(arr: ArrayLiteral) -> Self { |
||||
Self::ArrayLiteral(arr) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for ArrayLiteral { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
for expr in self.arr.iter().flatten() { |
||||
try_break!(visitor.visit_expression(expr)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
for expr in self.arr.iter_mut().flatten() { |
||||
try_break!(visitor.visit_expression_mut(expr)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
@ -0,0 +1,338 @@
|
||||
//! Object Expression.
|
||||
|
||||
use crate::{ |
||||
block_to_string, |
||||
expression::{operator::assign::AssignTarget, Expression, RESERVED_IDENTIFIERS_STRICT}, |
||||
join_nodes, |
||||
pattern::{ObjectPattern, ObjectPatternElement}, |
||||
property::{MethodDefinition, PropertyDefinition, PropertyName}, |
||||
try_break, |
||||
visitor::{VisitWith, Visitor, VisitorMut}, |
||||
ContainsSymbol, |
||||
}; |
||||
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; |
||||
use core::ops::ControlFlow; |
||||
|
||||
/// Objects in JavaScript may be defined as an unordered collection of related data, of
|
||||
/// primitive or reference types, in the form of “key: value” pairs.
|
||||
///
|
||||
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
|
||||
/// notation.
|
||||
///
|
||||
/// An object initializer is an expression that describes the initialization of an
|
||||
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
|
||||
/// Values of object properties can either contain [`primitive`][primitive] data types or other
|
||||
/// objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
|
||||
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||
#[cfg_attr(feature = "serde", serde(transparent))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct ObjectLiteral { |
||||
properties: Box<[PropertyDefinition]>, |
||||
} |
||||
|
||||
impl ObjectLiteral { |
||||
/// Gets the object literal properties
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn properties(&self) -> &[PropertyDefinition] { |
||||
&self.properties |
||||
} |
||||
|
||||
/// Converts the object literal into an [`ObjectPattern`].
|
||||
#[must_use] |
||||
pub fn to_pattern(&self, strict: bool) -> Option<ObjectPattern> { |
||||
let mut bindings = Vec::new(); |
||||
let mut excluded_keys = Vec::new(); |
||||
for (i, property) in self.properties.iter().enumerate() { |
||||
match property { |
||||
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => { |
||||
return None |
||||
} |
||||
PropertyDefinition::IdentifierReference(ident) => { |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) { |
||||
return None; |
||||
} |
||||
|
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(ident.sym()), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
PropertyDefinition::Property(name, expr) => match (name, expr) { |
||||
(PropertyName::Literal(name), Expression::Identifier(ident)) |
||||
if *name == *ident => |
||||
{ |
||||
if strict && *name == Sym::EVAL { |
||||
return None; |
||||
} |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) { |
||||
return None; |
||||
} |
||||
|
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(*name), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::Identifier(ident)) => { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(*name), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => { |
||||
let pattern = object.to_pattern(strict)?.into(); |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: PropertyName::Literal(*name), |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => { |
||||
let pattern = array.to_pattern(strict)?.into(); |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: PropertyName::Literal(*name), |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(_, Expression::Assign(assign)) => match assign.lhs() { |
||||
AssignTarget::Identifier(ident) => { |
||||
if let Some(name) = name.literal() { |
||||
if name == *ident { |
||||
if strict && name == Sym::EVAL { |
||||
return None; |
||||
} |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) { |
||||
return None; |
||||
} |
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(name), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} else { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(name), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
} else { |
||||
return None; |
||||
} |
||||
} |
||||
AssignTarget::Pattern(pattern) => { |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: name.clone(), |
||||
pattern: pattern.clone(), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
AssignTarget::Access(access) => { |
||||
bindings.push(ObjectPatternElement::AssignmentPropertyAccess { |
||||
name: name.clone(), |
||||
access: access.clone(), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
}, |
||||
(_, Expression::PropertyAccess(access)) => { |
||||
bindings.push(ObjectPatternElement::AssignmentPropertyAccess { |
||||
name: name.clone(), |
||||
access: access.clone(), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Computed(name), Expression::Identifier(ident)) => { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Computed(name.clone()), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
_ => return None, |
||||
}, |
||||
PropertyDefinition::SpreadObject(spread) => { |
||||
match spread { |
||||
Expression::Identifier(ident) => { |
||||
bindings.push(ObjectPatternElement::RestProperty { |
||||
ident: *ident, |
||||
excluded_keys: excluded_keys.clone(), |
||||
}); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess { |
||||
access: access.clone(), |
||||
excluded_keys: excluded_keys.clone(), |
||||
}); |
||||
} |
||||
_ => return None, |
||||
} |
||||
if i + 1 != self.properties.len() { |
||||
return None; |
||||
} |
||||
} |
||||
PropertyDefinition::MethodDefinition(_, _) => return None, |
||||
PropertyDefinition::CoverInitializedName(ident, expr) => { |
||||
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) { |
||||
return None; |
||||
} |
||||
|
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(ident.sym()), |
||||
default_init: Some(expr.clone()), |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Some(ObjectPattern::new(bindings.into())) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
self.properties |
||||
.iter() |
||||
.any(PropertyDefinition::contains_arguments) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
self.properties.iter().any(|prop| prop.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl ToIndentedString for ObjectLiteral { |
||||
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { |
||||
let mut buf = "{\n".to_owned(); |
||||
let indentation = " ".repeat(indent_n + 1); |
||||
for property in self.properties().iter() { |
||||
buf.push_str(&match property { |
||||
PropertyDefinition::IdentifierReference(ident) => { |
||||
format!("{indentation}{},\n", interner.resolve_expect(ident.sym())) |
||||
} |
||||
PropertyDefinition::Property(key, value) => { |
||||
format!( |
||||
"{indentation}{}: {},\n", |
||||
key.to_interned_string(interner), |
||||
value.to_no_indent_string(interner, indent_n + 1) |
||||
) |
||||
} |
||||
PropertyDefinition::SpreadObject(key) => { |
||||
format!("{indentation}...{},\n", key.to_interned_string(interner)) |
||||
} |
||||
PropertyDefinition::MethodDefinition(key, method) => { |
||||
format!( |
||||
"{indentation}{}{}({}) {},\n", |
||||
match &method { |
||||
MethodDefinition::Get(_) => "get ", |
||||
MethodDefinition::Set(_) => "set ", |
||||
_ => "", |
||||
}, |
||||
key.to_interned_string(interner), |
||||
match &method { |
||||
MethodDefinition::Get(expression) |
||||
| MethodDefinition::Set(expression) |
||||
| MethodDefinition::Ordinary(expression) => { |
||||
join_nodes(interner, expression.parameters().as_ref()) |
||||
} |
||||
MethodDefinition::Generator(expression) => { |
||||
join_nodes(interner, expression.parameters().as_ref()) |
||||
} |
||||
MethodDefinition::AsyncGenerator(expression) => { |
||||
join_nodes(interner, expression.parameters().as_ref()) |
||||
} |
||||
MethodDefinition::Async(expression) => { |
||||
join_nodes(interner, expression.parameters().as_ref()) |
||||
} |
||||
}, |
||||
match &method { |
||||
MethodDefinition::Get(expression) |
||||
| MethodDefinition::Set(expression) |
||||
| MethodDefinition::Ordinary(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::Generator(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::AsyncGenerator(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::Async(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
PropertyDefinition::CoverInitializedName(ident, expr) => { |
||||
format!( |
||||
"{indentation}{} = {},\n", |
||||
interner.resolve_expect(ident.sym()), |
||||
expr.to_no_indent_string(interner, indent_n + 1) |
||||
) |
||||
} |
||||
}); |
||||
} |
||||
buf.push_str(&format!("{}}}", " ".repeat(indent_n))); |
||||
|
||||
buf |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for ObjectLiteral |
||||
where |
||||
T: Into<Box<[PropertyDefinition]>>, |
||||
{ |
||||
#[inline] |
||||
fn from(props: T) -> Self { |
||||
Self { |
||||
properties: props.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ObjectLiteral> for Expression { |
||||
#[inline] |
||||
fn from(obj: ObjectLiteral) -> Self { |
||||
Self::ObjectLiteral(obj) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for ObjectLiteral { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
for pd in self.properties.iter() { |
||||
try_break!(visitor.visit_property_definition(pd)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
for pd in self.properties.iter_mut() { |
||||
try_break!(visitor.visit_property_definition_mut(pd)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
@ -0,0 +1,207 @@
|
||||
//! Assignment expression nodes, as defined by the [spec].
|
||||
//!
|
||||
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
|
||||
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
|
||||
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
|
||||
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
|
||||
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
|
||||
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
|
||||
|
||||
mod op; |
||||
|
||||
use core::ops::ControlFlow; |
||||
pub use op::*; |
||||
|
||||
use boa_interner::{Interner, Sym, ToInternedString}; |
||||
|
||||
use crate::try_break; |
||||
use crate::visitor::{VisitWith, Visitor, VisitorMut}; |
||||
use crate::{ |
||||
expression::{access::PropertyAccess, identifier::Identifier, Expression}, |
||||
pattern::Pattern, |
||||
ContainsSymbol, |
||||
}; |
||||
|
||||
/// An assignment operator expression.
|
||||
///
|
||||
/// See the [module level documentation][self] for more information.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct Assign { |
||||
op: AssignOp, |
||||
lhs: Box<AssignTarget>, |
||||
rhs: Box<Expression>, |
||||
} |
||||
|
||||
impl Assign { |
||||
/// Creates an `Assign` AST Expression.
|
||||
#[must_use] |
||||
pub fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self { |
||||
Self { |
||||
op, |
||||
lhs: Box::new(lhs), |
||||
rhs: Box::new(rhs), |
||||
} |
||||
} |
||||
|
||||
/// Gets the operator of the assignment operation.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn op(&self) -> AssignOp { |
||||
self.op |
||||
} |
||||
|
||||
/// Gets the left hand side of the assignment operation.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn lhs(&self) -> &AssignTarget { |
||||
&self.lhs |
||||
} |
||||
|
||||
/// Gets the right hand side of the assignment operation.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn rhs(&self) -> &Expression { |
||||
&self.rhs |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
(match &*self.lhs { |
||||
AssignTarget::Identifier(ident) => *ident == Sym::ARGUMENTS, |
||||
AssignTarget::Access(access) => access.contains_arguments(), |
||||
AssignTarget::Pattern(pattern) => pattern.contains_arguments(), |
||||
} || self.rhs.contains_arguments()) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
(match &*self.lhs { |
||||
AssignTarget::Identifier(_) => false, |
||||
AssignTarget::Access(access) => access.contains(symbol), |
||||
AssignTarget::Pattern(pattern) => pattern.contains(symbol), |
||||
} || self.rhs.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for Assign { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
format!( |
||||
"{} {} {}", |
||||
self.lhs.to_interned_string(interner), |
||||
self.op, |
||||
self.rhs.to_interned_string(interner) |
||||
) |
||||
} |
||||
} |
||||
|
||||
impl From<Assign> for Expression { |
||||
#[inline] |
||||
fn from(op: Assign) -> Self { |
||||
Self::Assign(op) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for Assign { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
try_break!(visitor.visit_assign_target(&self.lhs)); |
||||
visitor.visit_expression(&self.rhs) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
try_break!(visitor.visit_assign_target_mut(&mut self.lhs)); |
||||
visitor.visit_expression_mut(&mut self.rhs) |
||||
} |
||||
} |
||||
|
||||
/// The valid left-hand-side expressions of an assignment operator. Also called
|
||||
/// [`LeftHandSideExpression`][spec] in the spec.
|
||||
///
|
||||
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub enum AssignTarget { |
||||
/// A simple identifier, such as `a`.
|
||||
Identifier(Identifier), |
||||
/// A property access, such as `a.prop`.
|
||||
Access(PropertyAccess), |
||||
/// A pattern assignment, such as `{a, b, ...c}`.
|
||||
Pattern(Pattern), |
||||
} |
||||
|
||||
impl AssignTarget { |
||||
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
|
||||
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
|
||||
#[must_use] |
||||
pub fn from_expression( |
||||
expression: &Expression, |
||||
strict: bool, |
||||
destructure: bool, |
||||
) -> Option<Self> { |
||||
match expression { |
||||
Expression::Identifier(id) => Some(Self::Identifier(*id)), |
||||
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())), |
||||
Expression::ObjectLiteral(object) if destructure => { |
||||
let pattern = object.to_pattern(strict)?; |
||||
Some(Self::Pattern(pattern.into())) |
||||
} |
||||
Expression::ArrayLiteral(array) if destructure => { |
||||
let pattern = array.to_pattern(strict)?; |
||||
Some(Self::Pattern(pattern.into())) |
||||
} |
||||
_ => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for AssignTarget { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
match self { |
||||
AssignTarget::Identifier(id) => id.to_interned_string(interner), |
||||
AssignTarget::Access(access) => access.to_interned_string(interner), |
||||
AssignTarget::Pattern(pattern) => pattern.to_interned_string(interner), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<Identifier> for AssignTarget { |
||||
#[inline] |
||||
fn from(target: Identifier) -> Self { |
||||
Self::Identifier(target) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for AssignTarget { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
match self { |
||||
AssignTarget::Identifier(id) => visitor.visit_identifier(id), |
||||
AssignTarget::Access(pa) => visitor.visit_property_access(pa), |
||||
AssignTarget::Pattern(pat) => visitor.visit_pattern(pat), |
||||
} |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
match self { |
||||
AssignTarget::Identifier(id) => visitor.visit_identifier_mut(id), |
||||
AssignTarget::Access(pa) => visitor.visit_property_access_mut(pa), |
||||
AssignTarget::Pattern(pat) => visitor.visit_pattern_mut(pat), |
||||
} |
||||
} |
||||
} |
@ -1,144 +0,0 @@
|
||||
//! Array declaration Expression.
|
||||
|
||||
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut}; |
||||
use crate::syntax::ast::{expression::Expression, ContainsSymbol}; |
||||
use crate::try_break; |
||||
use boa_interner::{Interner, ToInternedString}; |
||||
use core::ops::ControlFlow; |
||||
|
||||
/// An array is an ordered collection of data (either primitive or object depending upon the
|
||||
/// language).
|
||||
///
|
||||
/// Arrays are used to store multiple values in a single variable.
|
||||
/// This is compared to a variable that can store only one value.
|
||||
///
|
||||
/// Each item in an array has a number attached to it, called a numeric index, that allows you
|
||||
/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
|
||||
/// methods.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct ArrayLiteral { |
||||
arr: Box<[Option<Expression>]>, |
||||
has_trailing_comma_spread: bool, |
||||
} |
||||
|
||||
impl ArrayLiteral { |
||||
/// Crate a new array literal.
|
||||
pub(crate) fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self |
||||
where |
||||
A: Into<Box<[Option<Expression>]>>, |
||||
{ |
||||
Self { |
||||
arr: array.into(), |
||||
has_trailing_comma_spread, |
||||
} |
||||
} |
||||
|
||||
/// Indicates if a spread operator in the array literal has a trailing comma.
|
||||
/// This is a syntax error in some cases.
|
||||
pub(crate) fn has_trailing_comma_spread(&self) -> bool { |
||||
self.has_trailing_comma_spread |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
self.arr |
||||
.iter() |
||||
.flatten() |
||||
.any(Expression::contains_arguments) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
self.arr.iter().flatten().any(|expr| expr.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl AsRef<[Option<Expression>]> for ArrayLiteral { |
||||
#[inline] |
||||
fn as_ref(&self) -> &[Option<Expression>] { |
||||
&self.arr |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for ArrayLiteral |
||||
where |
||||
T: Into<Box<[Option<Expression>]>>, |
||||
{ |
||||
#[inline] |
||||
fn from(decl: T) -> Self { |
||||
Self { |
||||
arr: decl.into(), |
||||
has_trailing_comma_spread: false, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for ArrayLiteral { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
let mut buf = String::from("["); |
||||
let mut first = true; |
||||
for e in &*self.arr { |
||||
if first { |
||||
first = false; |
||||
} else { |
||||
buf.push_str(", "); |
||||
} |
||||
if let Some(e) = e { |
||||
buf.push_str(&e.to_interned_string(interner)); |
||||
} |
||||
} |
||||
buf.push(']'); |
||||
buf |
||||
} |
||||
} |
||||
|
||||
impl From<ArrayLiteral> for Expression { |
||||
#[inline] |
||||
fn from(arr: ArrayLiteral) -> Self { |
||||
Self::ArrayLiteral(arr) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for ArrayLiteral { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
for expr in self.arr.iter().flatten() { |
||||
try_break!(visitor.visit_expression(expr)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
for expr in self.arr.iter_mut().flatten() { |
||||
try_break!(visitor.visit_expression_mut(expr)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
#[test] |
||||
fn fmt() { |
||||
crate::syntax::ast::test_formatting( |
||||
r#" |
||||
let a = [1, 2, 3, "words", "more words"]; |
||||
let b = []; |
||||
"#, |
||||
); |
||||
} |
||||
} |
@ -1,180 +0,0 @@
|
||||
//! Object Expression.
|
||||
|
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut}; |
||||
use crate::syntax::ast::{ |
||||
block_to_string, |
||||
expression::Expression, |
||||
join_nodes, |
||||
property::{MethodDefinition, PropertyDefinition}, |
||||
ContainsSymbol, |
||||
}; |
||||
use crate::try_break; |
||||
use boa_interner::{Interner, ToIndentedString, ToInternedString}; |
||||
use core::ops::ControlFlow; |
||||
|
||||
/// Objects in JavaScript may be defined as an unordered collection of related data, of
|
||||
/// primitive or reference types, in the form of “key: value” pairs.
|
||||
///
|
||||
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
|
||||
/// notation.
|
||||
///
|
||||
/// An object initializer is an expression that describes the initialization of an
|
||||
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
|
||||
/// Values of object properties can either contain [`primitive`][primitive] data types or other
|
||||
/// objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
|
||||
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
|
||||
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))] |
||||
#[cfg_attr(feature = "deser", serde(transparent))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct ObjectLiteral { |
||||
properties: Box<[PropertyDefinition]>, |
||||
} |
||||
|
||||
impl ObjectLiteral { |
||||
/// Gets the object literal properties
|
||||
#[inline] |
||||
pub fn properties(&self) -> &[PropertyDefinition] { |
||||
&self.properties |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
self.properties |
||||
.iter() |
||||
.any(PropertyDefinition::contains_arguments) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
self.properties.iter().any(|prop| prop.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl ToIndentedString for ObjectLiteral { |
||||
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { |
||||
let mut buf = "{\n".to_owned(); |
||||
let indentation = " ".repeat(indent_n + 1); |
||||
for property in self.properties().iter() { |
||||
buf.push_str(&match property { |
||||
PropertyDefinition::IdentifierReference(ident) => { |
||||
format!("{indentation}{},\n", interner.resolve_expect(ident.sym())) |
||||
} |
||||
PropertyDefinition::Property(key, value) => { |
||||
format!( |
||||
"{indentation}{}: {},\n", |
||||
key.to_interned_string(interner), |
||||
value.to_no_indent_string(interner, indent_n + 1) |
||||
) |
||||
} |
||||
PropertyDefinition::SpreadObject(key) => { |
||||
format!("{indentation}...{},\n", key.to_interned_string(interner)) |
||||
} |
||||
PropertyDefinition::MethodDefinition(key, method) => { |
||||
format!( |
||||
"{indentation}{}{}({}) {},\n", |
||||
match &method { |
||||
MethodDefinition::Get(_) => "get ", |
||||
MethodDefinition::Set(_) => "set ", |
||||
_ => "", |
||||
}, |
||||
key.to_interned_string(interner), |
||||
match &method { |
||||
MethodDefinition::Get(expression) |
||||
| MethodDefinition::Set(expression) |
||||
| MethodDefinition::Ordinary(expression) => { |
||||
join_nodes(interner, &expression.parameters().parameters) |
||||
} |
||||
MethodDefinition::Generator(expression) => { |
||||
join_nodes(interner, &expression.parameters().parameters) |
||||
} |
||||
MethodDefinition::AsyncGenerator(expression) => { |
||||
join_nodes(interner, &expression.parameters().parameters) |
||||
} |
||||
MethodDefinition::Async(expression) => { |
||||
join_nodes(interner, &expression.parameters().parameters) |
||||
} |
||||
}, |
||||
match &method { |
||||
MethodDefinition::Get(expression) |
||||
| MethodDefinition::Set(expression) |
||||
| MethodDefinition::Ordinary(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::Generator(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::AsyncGenerator(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
MethodDefinition::Async(expression) => { |
||||
block_to_string(expression.body(), interner, indent_n + 1) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
PropertyDefinition::CoverInitializedName(ident, expr) => { |
||||
format!( |
||||
"{indentation}{} = {},\n", |
||||
interner.resolve_expect(ident.sym()), |
||||
expr.to_no_indent_string(interner, indent_n + 1) |
||||
) |
||||
} |
||||
}); |
||||
} |
||||
buf.push_str(&format!("{}}}", " ".repeat(indent_n))); |
||||
|
||||
buf |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for ObjectLiteral |
||||
where |
||||
T: Into<Box<[PropertyDefinition]>>, |
||||
{ |
||||
#[inline] |
||||
fn from(props: T) -> Self { |
||||
Self { |
||||
properties: props.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ObjectLiteral> for Expression { |
||||
#[inline] |
||||
fn from(obj: ObjectLiteral) -> Self { |
||||
Self::ObjectLiteral(obj) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for ObjectLiteral { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
for pd in self.properties.iter() { |
||||
try_break!(visitor.visit_property_definition(pd)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
for pd in self.properties.iter_mut() { |
||||
try_break!(visitor.visit_property_definition_mut(pd)); |
||||
} |
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
@ -1,109 +0,0 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn spread_shallow_clone() { |
||||
let scenario = r#" |
||||
var a = { x: {} }; |
||||
var aClone = { ...a }; |
||||
|
||||
a.x === aClone.x |
||||
"#; |
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_merge() { |
||||
let scenario = r#" |
||||
var a = { x: 1, y: 2 }; |
||||
var b = { x: -1, z: -3, ...a }; |
||||
|
||||
(b.x === 1) && (b.y === 2) && (b.z === -3) |
||||
"#; |
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_overriding_properties() { |
||||
let scenario = r#" |
||||
var a = { x: 0, y: 0 }; |
||||
var aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; |
||||
|
||||
(aWithOverrides.x === 1) && (aWithOverrides.y === 2) |
||||
"#; |
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_getters_in_initializer() { |
||||
let scenario = r#" |
||||
var a = { x: 42 }; |
||||
var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } }; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "undefined"); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_getters_in_object() { |
||||
let scenario = r#" |
||||
var a = { x: 42 }; |
||||
var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } }; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "\"Error\": \"not thrown yet\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_setters() { |
||||
let scenario = r#" |
||||
var z = { set x(nexX) { throw new Error() }, ... { x: 1 } }; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "undefined"); |
||||
} |
||||
|
||||
#[test] |
||||
fn spread_null_and_undefined_ignored() { |
||||
let scenario = r#" |
||||
var a = { ...null, ...undefined }; |
||||
var count = 0; |
||||
|
||||
for (key in a) { count++; } |
||||
|
||||
count === 0 |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn fmt() { |
||||
crate::syntax::ast::test_formatting( |
||||
r#" |
||||
let other = { |
||||
c: 10, |
||||
}; |
||||
let inst = { |
||||
val: 5, |
||||
b: "hello world", |
||||
nested: { |
||||
a: 5, |
||||
b: 6, |
||||
}, |
||||
...other, |
||||
say_hi: function() { |
||||
console.log("hello!"); |
||||
}, |
||||
get a() { |
||||
return this.val + 1; |
||||
}, |
||||
set a(new_value) { |
||||
this.val = new_value; |
||||
}, |
||||
say_hello(msg) { |
||||
console.log("hello " + msg); |
||||
}, |
||||
}; |
||||
inst.a = 20; |
||||
inst.a; |
||||
inst.say_hello("humans"); |
||||
"#, |
||||
); |
||||
} |
@ -1,475 +0,0 @@
|
||||
//! Assignment expression nodes, as defined by the [spec].
|
||||
//!
|
||||
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
|
||||
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
|
||||
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
|
||||
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
|
||||
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
|
||||
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
|
||||
|
||||
mod op; |
||||
|
||||
use core::ops::ControlFlow; |
||||
pub use op::*; |
||||
|
||||
use boa_interner::{Interner, Sym, ToInternedString}; |
||||
|
||||
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut}; |
||||
use crate::syntax::{ |
||||
ast::{ |
||||
expression::{ |
||||
access::PropertyAccess, |
||||
identifier::Identifier, |
||||
literal::{ArrayLiteral, ObjectLiteral}, |
||||
Expression, |
||||
}, |
||||
pattern::{ |
||||
ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern, |
||||
}, |
||||
property::{PropertyDefinition, PropertyName}, |
||||
ContainsSymbol, |
||||
}, |
||||
parser::RESERVED_IDENTIFIERS_STRICT, |
||||
}; |
||||
use crate::try_break; |
||||
|
||||
/// An assignment operator expression.
|
||||
///
|
||||
/// See the [module level documentation][self] for more information.
|
||||
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub struct Assign { |
||||
op: AssignOp, |
||||
lhs: Box<AssignTarget>, |
||||
rhs: Box<Expression>, |
||||
} |
||||
|
||||
impl Assign { |
||||
/// Creates an `Assign` AST Expression.
|
||||
pub(in crate::syntax) fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self { |
||||
Self { |
||||
op, |
||||
lhs: Box::new(lhs), |
||||
rhs: Box::new(rhs), |
||||
} |
||||
} |
||||
|
||||
/// Gets the operator of the assignment operation.
|
||||
#[inline] |
||||
pub fn op(&self) -> AssignOp { |
||||
self.op |
||||
} |
||||
|
||||
/// Gets the left hand side of the assignment operation.
|
||||
#[inline] |
||||
pub fn lhs(&self) -> &AssignTarget { |
||||
&self.lhs |
||||
} |
||||
|
||||
/// Gets the right hand side of the assignment operation.
|
||||
#[inline] |
||||
pub fn rhs(&self) -> &Expression { |
||||
&self.rhs |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains_arguments(&self) -> bool { |
||||
(match &*self.lhs { |
||||
AssignTarget::Identifier(ident) => *ident == Sym::ARGUMENTS, |
||||
AssignTarget::Access(access) => access.contains_arguments(), |
||||
AssignTarget::Pattern(pattern) => pattern.contains_arguments(), |
||||
} || self.rhs.contains_arguments()) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { |
||||
(match &*self.lhs { |
||||
AssignTarget::Identifier(_) => false, |
||||
AssignTarget::Access(access) => access.contains(symbol), |
||||
AssignTarget::Pattern(pattern) => pattern.contains(symbol), |
||||
} || self.rhs.contains(symbol)) |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for Assign { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
format!( |
||||
"{} {} {}", |
||||
self.lhs.to_interned_string(interner), |
||||
self.op, |
||||
self.rhs.to_interned_string(interner) |
||||
) |
||||
} |
||||
} |
||||
|
||||
impl From<Assign> for Expression { |
||||
#[inline] |
||||
fn from(op: Assign) -> Self { |
||||
Self::Assign(op) |
||||
} |
||||
} |
||||
|
||||
impl VisitWith for Assign { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
try_break!(visitor.visit_assign_target(&self.lhs)); |
||||
visitor.visit_expression(&self.rhs) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
try_break!(visitor.visit_assign_target_mut(&mut self.lhs)); |
||||
visitor.visit_expression_mut(&mut self.rhs) |
||||
} |
||||
} |
||||
|
||||
/// The valid left-hand-side expressions of an assignment operator. Also called
|
||||
/// [`LeftHandSideExpression`][spec] in the spec.
|
||||
///
|
||||
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
|
||||
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq)] |
||||
pub enum AssignTarget { |
||||
/// A simple identifier, such as `a`.
|
||||
Identifier(Identifier), |
||||
/// A property access, such as `a.prop`.
|
||||
Access(PropertyAccess), |
||||
/// A pattern assignment, such as `{a, b, ...c}`.
|
||||
Pattern(Pattern), |
||||
} |
||||
|
||||
impl AssignTarget { |
||||
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
|
||||
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
|
||||
pub(crate) fn from_expression( |
||||
expression: &Expression, |
||||
strict: bool, |
||||
destructure: bool, |
||||
) -> Option<Self> { |
||||
match expression { |
||||
Expression::Identifier(id) => Some(Self::Identifier(*id)), |
||||
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())), |
||||
Expression::ObjectLiteral(object) if destructure => { |
||||
let pattern = object_decl_to_declaration_pattern(object, strict)?; |
||||
Some(Self::Pattern(pattern.into())) |
||||
} |
||||
Expression::ArrayLiteral(array) if destructure => { |
||||
let pattern = array_decl_to_declaration_pattern(array, strict)?; |
||||
Some(Self::Pattern(pattern.into())) |
||||
} |
||||
_ => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for AssignTarget { |
||||
#[inline] |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
match self { |
||||
AssignTarget::Identifier(id) => id.to_interned_string(interner), |
||||
AssignTarget::Access(access) => access.to_interned_string(interner), |
||||
AssignTarget::Pattern(pattern) => pattern.to_interned_string(interner), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<Identifier> for AssignTarget { |
||||
#[inline] |
||||
fn from(target: Identifier) -> Self { |
||||
Self::Identifier(target) |
||||
} |
||||
} |
||||
|
||||
/// Converts an object literal into an object declaration pattern.
|
||||
pub(crate) fn object_decl_to_declaration_pattern( |
||||
object: &ObjectLiteral, |
||||
strict: bool, |
||||
) -> Option<ObjectPattern> { |
||||
let mut bindings = Vec::new(); |
||||
let mut excluded_keys = Vec::new(); |
||||
for (i, property) in object.properties().iter().enumerate() { |
||||
match property { |
||||
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => { |
||||
return None |
||||
} |
||||
PropertyDefinition::IdentifierReference(ident) => { |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) { |
||||
return None; |
||||
} |
||||
|
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(ident.sym()), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
PropertyDefinition::Property(name, expr) => match (name, expr) { |
||||
(PropertyName::Literal(name), Expression::Identifier(ident)) if *name == *ident => { |
||||
if strict && *name == Sym::EVAL { |
||||
return None; |
||||
} |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) { |
||||
return None; |
||||
} |
||||
|
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(*name), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::Identifier(ident)) => { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(*name), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => { |
||||
let pattern = object_decl_to_declaration_pattern(object, strict)?.into(); |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: PropertyName::Literal(*name), |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => { |
||||
let pattern = array_decl_to_declaration_pattern(array, strict)?.into(); |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: PropertyName::Literal(*name), |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(_, Expression::Assign(assign)) => match assign.lhs() { |
||||
AssignTarget::Identifier(ident) => { |
||||
if let Some(name) = name.literal() { |
||||
if name == *ident { |
||||
if strict && name == Sym::EVAL { |
||||
return None; |
||||
} |
||||
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) { |
||||
return None; |
||||
} |
||||
excluded_keys.push(*ident); |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(name), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} else { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(name), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
} else { |
||||
return None; |
||||
} |
||||
} |
||||
AssignTarget::Pattern(pattern) => { |
||||
bindings.push(ObjectPatternElement::Pattern { |
||||
name: name.clone(), |
||||
pattern: pattern.clone(), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
AssignTarget::Access(access) => { |
||||
bindings.push(ObjectPatternElement::AssignmentPropertyAccess { |
||||
name: name.clone(), |
||||
access: access.clone(), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
}, |
||||
(_, Expression::PropertyAccess(access)) => { |
||||
bindings.push(ObjectPatternElement::AssignmentPropertyAccess { |
||||
name: name.clone(), |
||||
access: access.clone(), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
(PropertyName::Computed(name), Expression::Identifier(ident)) => { |
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Computed(name.clone()), |
||||
default_init: None, |
||||
}); |
||||
} |
||||
_ => return None, |
||||
}, |
||||
PropertyDefinition::SpreadObject(spread) => { |
||||
match spread { |
||||
Expression::Identifier(ident) => { |
||||
bindings.push(ObjectPatternElement::RestProperty { |
||||
ident: *ident, |
||||
excluded_keys: excluded_keys.clone(), |
||||
}); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess { |
||||
access: access.clone(), |
||||
excluded_keys: excluded_keys.clone(), |
||||
}); |
||||
} |
||||
_ => return None, |
||||
} |
||||
if i + 1 != object.properties().len() { |
||||
return None; |
||||
} |
||||
} |
||||
PropertyDefinition::MethodDefinition(_, _) => return None, |
||||
PropertyDefinition::CoverInitializedName(ident, expr) => { |
||||
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) { |
||||
return None; |
||||
} |
||||
|
||||
bindings.push(ObjectPatternElement::SingleName { |
||||
ident: *ident, |
||||
name: PropertyName::Literal(ident.sym()), |
||||
default_init: Some(expr.clone()), |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
Some(ObjectPattern::new(bindings.into())) |
||||
} |
||||
|
||||
/// Converts an array declaration into an array declaration pattern.
|
||||
pub(crate) fn array_decl_to_declaration_pattern( |
||||
array: &ArrayLiteral, |
||||
strict: bool, |
||||
) -> Option<ArrayPattern> { |
||||
if array.has_trailing_comma_spread() { |
||||
return None; |
||||
} |
||||
|
||||
let mut bindings = Vec::new(); |
||||
for (i, expr) in array.as_ref().iter().enumerate() { |
||||
let expr = if let Some(expr) = expr { |
||||
expr |
||||
} else { |
||||
bindings.push(ArrayPatternElement::Elision); |
||||
continue; |
||||
}; |
||||
match expr { |
||||
Expression::Identifier(ident) => { |
||||
if strict && *ident == Sym::ARGUMENTS { |
||||
return None; |
||||
} |
||||
|
||||
bindings.push(ArrayPatternElement::SingleName { |
||||
ident: *ident, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::Spread(spread) => { |
||||
match spread.target() { |
||||
Expression::Identifier(ident) => { |
||||
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident }); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccessRest { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
Expression::ArrayLiteral(array) => { |
||||
let pattern = array_decl_to_declaration_pattern(array, strict)?.into(); |
||||
bindings.push(ArrayPatternElement::PatternRest { pattern }); |
||||
} |
||||
Expression::ObjectLiteral(object) => { |
||||
let pattern = object_decl_to_declaration_pattern(object, strict)?.into(); |
||||
bindings.push(ArrayPatternElement::PatternRest { pattern }); |
||||
} |
||||
_ => return None, |
||||
} |
||||
if i + 1 != array.as_ref().len() { |
||||
return None; |
||||
} |
||||
} |
||||
Expression::Assign(assign) => match assign.lhs() { |
||||
AssignTarget::Identifier(ident) => { |
||||
bindings.push(ArrayPatternElement::SingleName { |
||||
ident: *ident, |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
AssignTarget::Access(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccess { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
AssignTarget::Pattern(pattern) => match pattern { |
||||
Pattern::Object(pattern) => { |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern: Pattern::Object(pattern.clone()), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
Pattern::Array(pattern) => { |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern: Pattern::Array(pattern.clone()), |
||||
default_init: Some(assign.rhs().clone()), |
||||
}); |
||||
} |
||||
}, |
||||
}, |
||||
Expression::ArrayLiteral(array) => { |
||||
let pattern = array_decl_to_declaration_pattern(array, strict)?.into(); |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::ObjectLiteral(object) => { |
||||
let pattern = object_decl_to_declaration_pattern(object, strict)?.into(); |
||||
bindings.push(ArrayPatternElement::Pattern { |
||||
pattern, |
||||
default_init: None, |
||||
}); |
||||
} |
||||
Expression::PropertyAccess(access) => { |
||||
bindings.push(ArrayPatternElement::PropertyAccess { |
||||
access: access.clone(), |
||||
}); |
||||
} |
||||
_ => return None, |
||||
} |
||||
} |
||||
Some(ArrayPattern::new(bindings.into())) |
||||
} |
||||
|
||||
impl VisitWith for AssignTarget { |
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
match self { |
||||
AssignTarget::Identifier(id) => visitor.visit_identifier(id), |
||||
AssignTarget::Access(pa) => visitor.visit_property_access(pa), |
||||
AssignTarget::Pattern(pat) => visitor.visit_pattern(pat), |
||||
} |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
match self { |
||||
AssignTarget::Identifier(id) => visitor.visit_identifier_mut(id), |
||||
AssignTarget::Access(pa) => visitor.visit_property_access_mut(pa), |
||||
AssignTarget::Pattern(pat) => visitor.visit_pattern_mut(pat), |
||||
} |
||||
} |
||||
} |
@ -1,140 +0,0 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn assignmentoperator_lhs_not_defined() { |
||||
let scenario = r#" |
||||
try { |
||||
a += 5 |
||||
} catch (err) { |
||||
err.toString() |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"ReferenceError: a is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn assignmentoperator_rhs_throws_error() { |
||||
let scenario = r#" |
||||
try { |
||||
let a; |
||||
a += b |
||||
} catch (err) { |
||||
err.toString() |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"ReferenceError: b is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn instanceofoperator_rhs_not_object() { |
||||
let scenario = r#" |
||||
try { |
||||
let s = new String(); |
||||
s instanceof 1 |
||||
} catch (err) { |
||||
err.toString() |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!( |
||||
&exec(scenario), |
||||
"\"TypeError: right-hand side of 'instanceof' should be an object, got number\"" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn instanceofoperator_rhs_not_callable() { |
||||
let scenario = r#" |
||||
try { |
||||
let s = new String(); |
||||
s instanceof {} |
||||
} catch (err) { |
||||
err.toString() |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!( |
||||
&exec(scenario), |
||||
"\"TypeError: right-hand side of 'instanceof' is not callable\"" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn logical_nullish_assignment() { |
||||
let scenario = r#" |
||||
let a = undefined; |
||||
a ??= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "10"); |
||||
|
||||
let scenario = r#" |
||||
let a = 20; |
||||
a ??= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn logical_assignment() { |
||||
let scenario = r#" |
||||
let a = false; |
||||
a &&= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "false"); |
||||
|
||||
let scenario = r#" |
||||
let a = 20; |
||||
a &&= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "10"); |
||||
|
||||
let scenario = r#" |
||||
let a = null; |
||||
a ||= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "10"); |
||||
let scenario = r#" |
||||
let a = 20; |
||||
a ||= 10; |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn fmt() { |
||||
crate::syntax::ast::test_formatting( |
||||
r#" |
||||
let a = 20; |
||||
a += 10; |
||||
a -= 10; |
||||
a *= 10; |
||||
a **= 10; |
||||
a /= 10; |
||||
a %= 10; |
||||
a &= 10; |
||||
a |= 10; |
||||
a ^= 10; |
||||
a <<= 10; |
||||
a >>= 10; |
||||
a >>>= 10; |
||||
a &&= 10; |
||||
a ||= 10; |
||||
a ??= 10; |
||||
a; |
||||
"#, |
||||
); |
||||
} |
@ -1,558 +0,0 @@
|
||||
use crate::{check_output, exec, syntax::ast::test_formatting, TestAction}; |
||||
|
||||
#[test] |
||||
fn while_loop_late_break() { |
||||
// Ordering with statement before the break.
|
||||
let scenario = r#" |
||||
let a = 1; |
||||
while (a < 5) { |
||||
a++; |
||||
if (a == 3) { |
||||
break; |
||||
} |
||||
} |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn while_loop_early_break() { |
||||
// Ordering with statements after the break.
|
||||
let scenario = r#" |
||||
let a = 1; |
||||
while (a < 5) { |
||||
if (a == 3) { |
||||
break; |
||||
} |
||||
a++; |
||||
} |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_loop_break() { |
||||
let scenario = r#" |
||||
let a = 1; |
||||
for (; a < 5; a++) { |
||||
if (a == 3) { |
||||
break; |
||||
} |
||||
} |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_loop_return() { |
||||
let scenario = r#" |
||||
function foo() { |
||||
for (let a = 1; a < 5; a++) { |
||||
if (a == 3) { |
||||
return a; |
||||
} |
||||
} |
||||
} |
||||
|
||||
foo(); |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn do_loop_late_break() { |
||||
// Ordering with statement before the break.
|
||||
let scenario = r#" |
||||
let a = 1; |
||||
do { |
||||
a++; |
||||
if (a == 3) { |
||||
break; |
||||
} |
||||
} while (a < 5); |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn do_loop_early_break() { |
||||
// Ordering with statements after the break.
|
||||
let scenario = r#" |
||||
let a = 1; |
||||
do { |
||||
if (a == 3) { |
||||
break; |
||||
} |
||||
a++; |
||||
} while (a < 5); |
||||
a; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn break_out_of_inner_loop() { |
||||
let scenario = r#" |
||||
var a = 0, b = 0; |
||||
for (let i = 0; i < 2; i++) { |
||||
a++; |
||||
for (let j = 0; j < 10; j++) { |
||||
b++; |
||||
if (j == 3) |
||||
break; |
||||
} |
||||
a++; |
||||
} |
||||
[a, b] |
||||
"#; |
||||
assert_eq!(&exec(scenario), "[ 4, 8 ]"); |
||||
} |
||||
|
||||
#[test] |
||||
fn continue_inner_loop() { |
||||
let scenario = r#" |
||||
var a = 0, b = 0; |
||||
for (let i = 0; i < 2; i++) { |
||||
a++; |
||||
for (let j = 0; j < 10; j++) { |
||||
if (j < 3) |
||||
continue; |
||||
b++; |
||||
} |
||||
a++; |
||||
} |
||||
[a, b] |
||||
"#; |
||||
assert_eq!(&exec(scenario), "[ 4, 14 ]"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_loop_continue_out_of_switch() { |
||||
let scenario = r#" |
||||
var a = 0, b = 0, c = 0; |
||||
for (let i = 0; i < 3; i++) { |
||||
a++; |
||||
switch (i) { |
||||
case 0: |
||||
continue; |
||||
c++; |
||||
case 1: |
||||
continue; |
||||
case 5: |
||||
c++; |
||||
} |
||||
b++; |
||||
} |
||||
[a, b, c] |
||||
"#; |
||||
assert_eq!(&exec(scenario), "[ 3, 1, 0 ]"); |
||||
} |
||||
|
||||
#[test] |
||||
fn while_loop_continue() { |
||||
let scenario = r#" |
||||
var i = 0, a = 0, b = 0; |
||||
while (i < 3) { |
||||
i++; |
||||
if (i < 2) { |
||||
a++; |
||||
continue; |
||||
} |
||||
b++; |
||||
} |
||||
[a, b] |
||||
"#; |
||||
assert_eq!(&exec(scenario), "[ 1, 2 ]"); |
||||
} |
||||
|
||||
#[test] |
||||
fn do_while_loop_continue() { |
||||
let scenario = r#" |
||||
var i = 0, a = 0, b = 0; |
||||
do { |
||||
i++; |
||||
if (i < 2) { |
||||
a++; |
||||
continue; |
||||
} |
||||
b++; |
||||
} while (i < 3) |
||||
[a, b] |
||||
"#; |
||||
assert_eq!(&exec(scenario), "[ 1, 2 ]"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_declaration() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (i of [1, 2, 3]) { |
||||
result = i; |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "3"), |
||||
TestAction::TestEq("i", "3"), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_var() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (var i of [1, 2, 3]) { |
||||
result = i; |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "3"), |
||||
TestAction::TestEq("i", "3"), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_let() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (let i of [1, 2, 3]) { |
||||
result = i; |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "3"), |
||||
TestAction::TestEq( |
||||
r#" |
||||
try { |
||||
i |
||||
} catch(e) { |
||||
e.toString() |
||||
} |
||||
"#, |
||||
"\"ReferenceError: i is not defined\"", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_const() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (let i of [1, 2, 3]) { |
||||
result = i; |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "3"), |
||||
TestAction::TestEq( |
||||
r#" |
||||
try { |
||||
i |
||||
} catch(e) { |
||||
e.toString() |
||||
} |
||||
"#, |
||||
"\"ReferenceError: i is not defined\"", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_break() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (var i of [1, 2, 3]) { |
||||
if (i > 1) |
||||
break; |
||||
result = i |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "1"), |
||||
TestAction::TestEq("i", "2"), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_continue() { |
||||
let scenario = r#" |
||||
var result = 0; |
||||
for (var i of [1, 2, 3]) { |
||||
if (i == 3) |
||||
continue; |
||||
result = i |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("result", "2"), |
||||
TestAction::TestEq("i", "3"), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_of_loop_return() { |
||||
let scenario = r#" |
||||
function foo() { |
||||
for (i of [1, 2, 3]) { |
||||
if (i > 1) |
||||
return i; |
||||
} |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(scenario), |
||||
TestAction::TestEq("foo()", "2"), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_loop_break_label() { |
||||
let scenario = r#" |
||||
var str = ""; |
||||
|
||||
outer: for (let i = 0; i < 5; i++) { |
||||
inner: for (let b = 0; b < 5; b++) { |
||||
if (b === 2) { |
||||
break outer; |
||||
} |
||||
str = str + b; |
||||
} |
||||
str = str + i; |
||||
} |
||||
str |
||||
"#; |
||||
assert_eq!(&exec(scenario), "\"01\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_loop_continue_label() { |
||||
let scenario = r#" |
||||
var count = 0; |
||||
label: for (let x = 0; x < 10;) { |
||||
while (true) { |
||||
x++; |
||||
count++; |
||||
continue label; |
||||
} |
||||
} |
||||
count |
||||
"#; |
||||
assert_eq!(&exec(scenario), "10"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_declaration() { |
||||
let init = r#" |
||||
let result = []; |
||||
let obj = { a: "a", b: 2}; |
||||
var i; |
||||
for (i in obj) { |
||||
result = result.concat([i]); |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(init), |
||||
TestAction::TestEq( |
||||
"result.length === 2 && result.includes('a') && result.includes('b')", |
||||
"true", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_var_object() { |
||||
let init = r#" |
||||
let result = []; |
||||
let obj = { a: "a", b: 2}; |
||||
for (var i in obj) { |
||||
result = result.concat([i]); |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(init), |
||||
TestAction::TestEq( |
||||
"result.length === 2 && result.includes('a') && result.includes('b')", |
||||
"true", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_var_array() { |
||||
let init = r#" |
||||
let result = []; |
||||
let arr = ["a", "b"]; |
||||
for (var i in arr) { |
||||
result = result.concat([i]); |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(init), |
||||
TestAction::TestEq( |
||||
"result.length === 2 && result.includes('0') && result.includes('1')", |
||||
"true", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_let_object() { |
||||
let init = r#" |
||||
let result = []; |
||||
let obj = { a: "a", b: 2}; |
||||
for (let i in obj) { |
||||
result = result.concat([i]); |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(init), |
||||
TestAction::TestEq( |
||||
"result.length === 2 && result.includes('a') && result.includes('b')", |
||||
"true", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_const_array() { |
||||
let init = r#" |
||||
let result = []; |
||||
let arr = ["a", "b"]; |
||||
for (const i in arr) { |
||||
result = result.concat([i]); |
||||
} |
||||
"#; |
||||
check_output(&[ |
||||
TestAction::Execute(init), |
||||
TestAction::TestEq( |
||||
"result.length === 2 && result.includes('0') && result.includes('1')", |
||||
"true", |
||||
), |
||||
]); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_break_label() { |
||||
let scenario = r#" |
||||
var str = ""; |
||||
|
||||
outer: for (let i in [1, 2]) { |
||||
inner: for (let b in [2, 3, 4]) { |
||||
if (b === "1") { |
||||
break outer; |
||||
} |
||||
str = str + b; |
||||
} |
||||
str = str + i; |
||||
} |
||||
str |
||||
"#; |
||||
assert_eq!(&exec(scenario), "\"0\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_in_continue_label() { |
||||
let scenario = r#" |
||||
var str = ""; |
||||
|
||||
outer: for (let i in [1, 2]) { |
||||
inner: for (let b in [2, 3, 4]) { |
||||
if (b === "1") { |
||||
continue outer; |
||||
} |
||||
str = str + b; |
||||
} |
||||
str = str + i; |
||||
} |
||||
str |
||||
"#; |
||||
assert_eq!(&exec(scenario), "\"00\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn fmt() { |
||||
// Labeled and unlabeled for in loops
|
||||
test_formatting( |
||||
r#" |
||||
var str = ""; |
||||
outer: for (let i in [1, 2]) { |
||||
for (let b in [2, 3, 4]) { |
||||
if (b === "1") { |
||||
continue outer; |
||||
} |
||||
str = str + b; |
||||
} |
||||
str = str + i; |
||||
} |
||||
str; |
||||
"#, |
||||
); |
||||
// Labeled and unlabeled for loops
|
||||
test_formatting( |
||||
r#" |
||||
var str = ""; |
||||
outer: for (let i = 0; i < 10; ++i) { |
||||
for (let j = 3; j < 6; ++j) { |
||||
if (j === "1") { |
||||
continue outer; |
||||
} |
||||
str = str + j; |
||||
} |
||||
str = str + i; |
||||
} |
||||
str; |
||||
"#, |
||||
); |
||||
// Labeled and unlabeled for of loops
|
||||
test_formatting( |
||||
r#" |
||||
for (i of [1, 2, 3]) { |
||||
if (false) { |
||||
break; |
||||
} |
||||
} |
||||
label: for (i of [1, 2, 3]) { |
||||
if (false) { |
||||
break label; |
||||
} |
||||
} |
||||
"#, |
||||
); |
||||
// Labeled and unlabeled do while loops
|
||||
test_formatting( |
||||
r#" |
||||
do { |
||||
break; |
||||
} while (true); |
||||
label: do { |
||||
break label; |
||||
} while (true); |
||||
"#, |
||||
); |
||||
// Labeled and unlabeled while loops
|
||||
test_formatting( |
||||
r#" |
||||
while (true) { |
||||
break; |
||||
} |
||||
label: while (true) { |
||||
break label; |
||||
} |
||||
"#, |
||||
); |
||||
} |
@ -1,243 +0,0 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn single_case_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
switch (a) { |
||||
case 10: |
||||
a = 20; |
||||
break; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn no_cases_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
switch (a) { |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "10"); |
||||
} |
||||
|
||||
#[test] |
||||
fn no_true_case_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
switch (a) { |
||||
case 5: |
||||
a = 15; |
||||
break; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "10"); |
||||
} |
||||
|
||||
#[test] |
||||
fn two_case_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
switch (a) { |
||||
case 5: |
||||
a = 15; |
||||
break; |
||||
case 10: |
||||
a = 20; |
||||
break; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn two_case_no_break_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
let b = 10; |
||||
|
||||
switch (a) { |
||||
case 10: |
||||
a = 150; |
||||
case 20: |
||||
b = 150; |
||||
break; |
||||
} |
||||
|
||||
a + b; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "300"); |
||||
} |
||||
|
||||
#[test] |
||||
fn three_case_partial_fallthrough() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
let b = 10; |
||||
|
||||
switch (a) { |
||||
case 10: |
||||
a = 150; |
||||
case 20: |
||||
b = 150; |
||||
break; |
||||
case 15: |
||||
b = 1000; |
||||
break; |
||||
} |
||||
|
||||
a + b; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "300"); |
||||
} |
||||
|
||||
#[test] |
||||
fn default_taken_switch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
|
||||
switch (a) { |
||||
case 5: |
||||
a = 150; |
||||
break; |
||||
default: |
||||
a = 70; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "70"); |
||||
} |
||||
|
||||
#[test] |
||||
fn default_not_taken_switch() { |
||||
let scenario = r#" |
||||
let a = 5; |
||||
|
||||
switch (a) { |
||||
case 5: |
||||
a = 150; |
||||
break; |
||||
default: |
||||
a = 70; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "150"); |
||||
} |
||||
|
||||
#[test] |
||||
fn string_switch() { |
||||
let scenario = r#" |
||||
let a = "hello"; |
||||
|
||||
switch (a) { |
||||
case "hello": |
||||
a = "world"; |
||||
break; |
||||
default: |
||||
a = "hi"; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "\"world\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn bigger_switch_example() { |
||||
let expected = [ |
||||
"\"Mon\"", |
||||
"\"Tue\"", |
||||
"\"Wed\"", |
||||
"\"Thurs\"", |
||||
"\"Fri\"", |
||||
"\"Sat\"", |
||||
"\"Sun\"", |
||||
]; |
||||
|
||||
for (i, val) in expected.iter().enumerate() { |
||||
let scenario = format!( |
||||
r#" |
||||
let a = {i}; |
||||
let b = "unknown"; |
||||
|
||||
switch (a) {{ |
||||
case 0: |
||||
b = "Mon"; |
||||
break; |
||||
case 1: |
||||
b = "Tue"; |
||||
break; |
||||
case 2: |
||||
b = "Wed"; |
||||
break; |
||||
case 3: |
||||
b = "Thurs"; |
||||
break; |
||||
case 4: |
||||
b = "Fri"; |
||||
break; |
||||
case 5: |
||||
b = "Sat"; |
||||
break; |
||||
case 6: |
||||
b = "Sun"; |
||||
break;
|
||||
}} |
||||
|
||||
b; |
||||
|
||||
"#, |
||||
); |
||||
|
||||
assert_eq!(&exec(&scenario), val); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn fmt() { |
||||
crate::syntax::ast::test_formatting( |
||||
r#" |
||||
let a = 3; |
||||
let b = "unknown"; |
||||
switch (a) { |
||||
case 0: |
||||
b = "Mon"; |
||||
break; |
||||
case 1: |
||||
b = "Tue"; |
||||
break; |
||||
case 2: |
||||
b = "Wed"; |
||||
break; |
||||
case 3: |
||||
b = "Thurs"; |
||||
break; |
||||
case 4: |
||||
b = "Fri"; |
||||
break; |
||||
case 5: |
||||
b = "Sat"; |
||||
break; |
||||
case 6: |
||||
b = "Sun"; |
||||
break; |
||||
default: |
||||
b = "Unknown"; |
||||
} |
||||
b; |
||||
"#, |
||||
); |
||||
} |
@ -1,147 +0,0 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn simple_try() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} catch { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} catch { |
||||
a = 40; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw "error"; |
||||
} catch { |
||||
a = 20; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw 20; |
||||
} catch(err) { |
||||
a = err; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding_pattern_object() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw { |
||||
n: 30, |
||||
}; |
||||
} catch ({ n }) { |
||||
a = n; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding_pattern_array() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw [20, 30]; |
||||
} catch ([, n]) { |
||||
a = n; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding_finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw 20; |
||||
} catch(err) { |
||||
a = err; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn fmt() { |
||||
crate::syntax::ast::test_formatting( |
||||
r#" |
||||
try { |
||||
throw "hello"; |
||||
} catch(e) { |
||||
console.log(e); |
||||
} finally { |
||||
console.log("things"); |
||||
} |
||||
try { |
||||
throw "hello"; |
||||
} catch { |
||||
console.log("something went wrong"); |
||||
} |
||||
"#, |
||||
); |
||||
} |
@ -1,118 +0,0 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn strict_mode_global() { |
||||
let scenario = r#" |
||||
'use strict'; |
||||
let throws = false; |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn strict_mode_function() { |
||||
let scenario = r#" |
||||
let throws = false; |
||||
function t() { |
||||
'use strict'; |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
} |
||||
t() |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn strict_mode_function_after() { |
||||
let scenario = r#" |
||||
function t() { |
||||
'use strict'; |
||||
} |
||||
t() |
||||
let throws = false; |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn strict_mode_global_active_in_function() { |
||||
let scenario = r#" |
||||
'use strict' |
||||
let throws = false; |
||||
function a(){ |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
} |
||||
a(); |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn strict_mode_function_in_function() { |
||||
let scenario = r#" |
||||
let throws = false; |
||||
function a(){ |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
} |
||||
function b(){ |
||||
'use strict'; |
||||
a(); |
||||
} |
||||
b(); |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn strict_mode_function_return() { |
||||
let scenario = r#" |
||||
let throws = false; |
||||
function a() { |
||||
'use strict'; |
||||
|
||||
return function () { |
||||
try { |
||||
delete Boolean.prototype; |
||||
} catch (e) { |
||||
throws = true; |
||||
} |
||||
} |
||||
} |
||||
a()(); |
||||
throws |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue