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