Browse Source

Move redeclaration errors to parser (#2027)

This Pull Request changes the following:

- Implement redeclaration errors in the parser
- Remove redeclaration errors from the compiler (this is a big step towards #1907)
- Fix some failing tests on the way

This requires a slight change in our public api. The Parser new requires a full `Context` instead of just the `Interner` for parsing new code. This is required, because if multiple scripts are parsed (e.g. every input in the REPL) global variables must be checked for redeclarations.
pull/2048/head
raskad 3 years ago
parent
commit
8b66988574
  1. 14
      boa_cli/src/main.rs
  2. 86
      boa_engine/src/bytecompiler.rs
  3. 10
      boa_engine/src/context/mod.rs
  4. 59
      boa_engine/src/environments/compile.rs
  5. 5
      boa_engine/src/realm.rs
  6. 10
      boa_engine/src/syntax/ast/node/block/mod.rs
  7. 23
      boa_engine/src/syntax/ast/node/iteration/mod.rs
  8. 123
      boa_engine/src/syntax/ast/node/mod.rs
  9. 21
      boa_engine/src/syntax/ast/node/parameters.rs
  10. 134
      boa_engine/src/syntax/ast/node/statement_list/mod.rs
  11. 26
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  12. 27
      boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs
  13. 24
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs
  14. 4
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs
  15. 24
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs
  16. 4
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs
  17. 24
      boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs
  18. 4
      boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs
  19. 24
      boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs
  20. 4
      boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs
  21. 93
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  22. 24
      boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs
  23. 5
      boa_engine/src/syntax/parser/expression/primary/tests.rs
  24. 88
      boa_engine/src/syntax/parser/expression/tests.rs
  25. 59
      boa_engine/src/syntax/parser/function/tests.rs
  26. 78
      boa_engine/src/syntax/parser/mod.rs
  27. 39
      boa_engine/src/syntax/parser/statement/block/mod.rs
  28. 15
      boa_engine/src/syntax/parser/statement/block/tests.rs
  29. 26
      boa_engine/src/syntax/parser/statement/break_stm/tests.rs
  30. 26
      boa_engine/src/syntax/parser/statement/continue_stm/tests.rs
  31. 6
      boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs
  32. 2
      boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs
  33. 39
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  34. 6
      boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs
  35. 2
      boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs
  36. 24
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  37. 34
      boa_engine/src/syntax/parser/statement/declaration/tests.rs
  38. 6
      boa_engine/src/syntax/parser/statement/if_stm/tests.rs
  39. 58
      boa_engine/src/syntax/parser/statement/iteration/for_statement.rs
  40. 12
      boa_engine/src/syntax/parser/statement/iteration/tests.rs
  41. 106
      boa_engine/src/syntax/parser/statement/mod.rs
  42. 48
      boa_engine/src/syntax/parser/statement/switch/mod.rs
  43. 2
      boa_engine/src/syntax/parser/statement/switch/tests.rs
  44. 2
      boa_engine/src/syntax/parser/statement/throw/tests.rs
  45. 100
      boa_engine/src/syntax/parser/statement/try_stm/catch.rs
  46. 54
      boa_engine/src/syntax/parser/statement/try_stm/tests.rs
  47. 69
      boa_engine/src/syntax/parser/tests.rs
  48. 28
      boa_tester/src/exec/mod.rs

14
boa_cli/src/main.rs

@ -60,7 +60,6 @@
)]
use boa_engine::{syntax::ast::node::StatementList, Context};
use boa_interner::Interner;
use clap::{ArgEnum, Parser};
use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
@ -135,7 +134,7 @@ enum DumpFormat {
///
/// Returns a error of type String with a message,
/// if the token stream has a parsing error.
fn parse_tokens<S>(src: S, interner: &mut Interner) -> Result<StatementList, String>
fn parse_tokens<S>(src: S, context: &mut Context) -> Result<StatementList, String>
where
S: AsRef<[u8]>,
{
@ -143,7 +142,7 @@ where
let src_bytes = src.as_ref();
Parser::new(src_bytes, false)
.parse_all(interner)
.parse_all(context)
.map_err(|e| format!("ParsingError: {e}"))
}
@ -151,13 +150,12 @@ where
///
/// Returns a error of type String with a error message,
/// if the source has a syntax or parsing error.
fn dump<S>(src: S, args: &Opt) -> Result<(), String>
fn dump<S>(src: S, args: &Opt, context: &mut Context) -> Result<(), String>
where
S: AsRef<[u8]>,
{
if let Some(ref arg) = args.dump_ast {
let mut interner = Interner::default();
let ast = parse_tokens(src, &mut interner)?;
let ast = parse_tokens(src, context)?;
match arg {
Some(format) => match format {
@ -194,7 +192,7 @@ pub fn main() -> Result<(), std::io::Error> {
let buffer = read(file)?;
if args.has_dump_flag() {
if let Err(e) = dump(&buffer, &args) {
if let Err(e) = dump(&buffer, &args, &mut context) {
eprintln!("{e}");
}
} else {
@ -233,7 +231,7 @@ pub fn main() -> Result<(), std::io::Error> {
editor.add_history_entry(&line);
if args.has_dump_flag() {
if let Err(e) = dump(&line, &args) {
if let Err(e) = dump(&line, &args, &mut context) {
eprintln!("{e}");
}
} else {

86
boa_engine/src/bytecompiler.rs

@ -1317,53 +1317,50 @@ impl<'b> ByteCompiler<'b> {
match for_in_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => {
self.context
.create_mutable_binding(ident.sym(), true, true)?;
self.context.create_mutable_binding(ident.sym(), true);
let binding = self.context.set_mutable_binding(ident.sym());
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context
.create_mutable_binding(ident.sym(), true, true)?;
self.context.create_mutable_binding(ident.sym(), true);
self.emit_binding(BindingOpcode::InitVar, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context
.create_mutable_binding(ident.sym(), false, false)?;
self.context.create_mutable_binding(ident.sym(), false);
self.emit_binding(BindingOpcode::InitLet, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, false, false)?;
self.context.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context.create_immutable_binding(ident.sym())?;
self.context.create_immutable_binding(ident.sym());
self.emit_binding(BindingOpcode::InitConst, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_immutable_binding(ident)?;
self.context.create_immutable_binding(ident);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::DeclarationPattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
@ -1400,53 +1397,50 @@ impl<'b> ByteCompiler<'b> {
match for_of_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => {
self.context
.create_mutable_binding(ident.sym(), true, true)?;
self.context.create_mutable_binding(ident.sym(), true);
let binding = self.context.set_mutable_binding(ident.sym());
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context
.create_mutable_binding(ident.sym(), true, true)?;
self.context.create_mutable_binding(ident.sym(), true);
self.emit_binding(BindingOpcode::InitVar, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context
.create_mutable_binding(ident.sym(), false, false)?;
self.context.create_mutable_binding(ident.sym(), false);
self.emit_binding(BindingOpcode::InitLet, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, false, false)?;
self.context.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
self.context.create_immutable_binding(ident.sym())?;
self.context.create_immutable_binding(ident.sym());
self.emit_binding(BindingOpcode::InitConst, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_immutable_binding(ident)?;
self.context.create_immutable_binding(ident);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::DeclarationPattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
@ -1717,13 +1711,12 @@ impl<'b> ByteCompiler<'b> {
if let Some(decl) = catch.parameter() {
match decl {
Declaration::Identifier { ident, .. } => {
self.context
.create_mutable_binding(ident.sym(), false, false)?;
self.context.create_mutable_binding(ident.sym(), false);
self.emit_binding(BindingOpcode::InitLet, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, false, false)?;
self.context.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
@ -1869,7 +1862,7 @@ impl<'b> ByteCompiler<'b> {
if !(kind == FunctionKind::Arrow) && !parameters.has_arguments() {
compiler
.context
.create_mutable_binding(Sym::ARGUMENTS, false, true)?;
.create_mutable_binding(Sym::ARGUMENTS, false);
compiler.code_block.arguments_binding = Some(
compiler
.context
@ -1884,9 +1877,7 @@ impl<'b> ByteCompiler<'b> {
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
compiler
.context
.create_mutable_binding(ident.sym(), false, true)?;
compiler.context.create_mutable_binding(ident.sym(), false);
if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?;
@ -1896,9 +1887,7 @@ impl<'b> ByteCompiler<'b> {
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
compiler
.context
.create_mutable_binding(ident, false, true)?;
compiler.context.create_mutable_binding(ident, false);
}
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?;
}
@ -2288,14 +2277,14 @@ impl<'b> ByteCompiler<'b> {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
}
}
@ -2309,14 +2298,14 @@ impl<'b> ByteCompiler<'b> {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, false, false)?;
self.context.create_mutable_binding(ident, false);
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, false, false)?;
self.context.create_mutable_binding(ident, false);
}
}
}
@ -2330,50 +2319,49 @@ impl<'b> ByteCompiler<'b> {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_immutable_binding(ident)?;
self.context.create_immutable_binding(ident);
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_immutable_binding(ident)?;
self.context.create_immutable_binding(ident);
}
}
}
}
}
Node::ClassDecl(decl) => {
self.context
.create_mutable_binding(decl.name(), false, false)?;
self.context.create_mutable_binding(decl.name(), false);
}
Node::FunctionDecl(decl) => {
let ident = decl.name();
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
Node::GeneratorDecl(decl) => {
let ident = decl.name();
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
Node::AsyncFunctionDecl(decl) => {
let ident = decl.name();
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
Node::AsyncGeneratorDecl(decl) => {
let ident = decl.name();
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.context.create_mutable_binding(ident, true, true)?;
self.context.create_mutable_binding(ident, true);
}
Node::DoWhileLoop(do_while_loop) => {
if !matches!(do_while_loop.body(), Node::Block(_)) {
@ -2459,7 +2447,7 @@ impl<'b> ByteCompiler<'b> {
compiler.code_block.params = expr.parameters().clone();
compiler
.context
.create_mutable_binding(Sym::ARGUMENTS, false, true)?;
.create_mutable_binding(Sym::ARGUMENTS, false);
compiler.code_block.arguments_binding = Some(
compiler
.context
@ -2472,9 +2460,7 @@ impl<'b> ByteCompiler<'b> {
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
compiler
.context
.create_mutable_binding(ident.sym(), false, true)?;
compiler.context.create_mutable_binding(ident.sym(), false);
if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?;
@ -2484,9 +2470,7 @@ impl<'b> ByteCompiler<'b> {
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
compiler
.context
.create_mutable_binding(ident, false, true)?;
compiler.context.create_mutable_binding(ident, false);
}
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?;
}

10
boa_engine/src/context/mod.rs

@ -182,7 +182,7 @@ impl Context {
where
S: AsRef<[u8]>,
{
Parser::new(src.as_ref(), self.strict).parse_all(&mut self.interner)
Parser::new(src.as_ref(), self.strict).parse_all(self)
}
/// <https://tc39.es/ecma262/#sec-call>
@ -204,12 +204,6 @@ impl Context {
self.realm.global_object()
}
/// Return a reference to the global object string bindings.
#[inline]
pub(crate) fn global_bindings(&self) -> &GlobalPropertyMap {
self.realm.global_bindings()
}
/// Return a mutable reference to the global object string bindings.
#[inline]
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
@ -648,7 +642,7 @@ impl Context {
let main_timer = Profiler::global().start_event("Evaluation", "Main");
let parsing_result = Parser::new(src.as_ref(), false)
.parse_all(&mut self.interner)
.parse_all(self)
.map_err(|e| e.to_string());
let statement_list = match parsing_result {

59
boa_engine/src/environments/compile.rs

@ -1,6 +1,5 @@
use crate::{
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsResult,
JsString, JsValue,
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue,
};
use boa_interner::Sym;
use rustc_hash::FxHashMap;
@ -154,22 +153,13 @@ impl Context {
///
/// Panics if the global environment is not function scoped.
#[inline]
pub(crate) fn create_mutable_binding(
&mut self,
name: Sym,
function_scope: bool,
allow_name_reuse: bool,
) -> JsResult<()> {
pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) {
let name_str = JsString::from(self.interner().resolve_expect(name));
for (i, env) in self.realm.compile_env.stack.iter_mut().enumerate().rev() {
if !function_scope || env.function_scope {
if env.bindings.contains_key(&name) {
if allow_name_reuse {
return Ok(());
}
return self
.throw_syntax_error(format!("Redeclaration of variable {}", name_str));
return;
}
if i == 0 {
@ -178,10 +168,6 @@ impl Context {
.global_property_map
.string_property_map()
.get(&name_str);
let non_configurable_binding_exists = match desc {
Some(desc) => !matches!(desc.configurable(), Some(true)),
None => false,
};
if function_scope && desc.is_none() {
self.global_bindings_mut().insert(
name_str,
@ -192,15 +178,9 @@ impl Context {
.configurable(true)
.build(),
);
return Ok(());
return;
} else if function_scope {
return Ok(());
} else if !function_scope
&& !allow_name_reuse
&& non_configurable_binding_exists
{
return self
.throw_syntax_error(format!("Redeclaration of variable {}", name_str));
return;
}
}
@ -212,7 +192,7 @@ impl Context {
mutable: true,
},
);
return Ok(());
return;
}
continue;
}
@ -249,11 +229,7 @@ impl Context {
///
/// Panics if the global environment does not exist.
#[inline]
pub(crate) fn create_immutable_binding(&mut self, name: Sym) -> JsResult<()> {
let name_str = JsString::from(self.interner().resolve_expect(name));
let exists_global = self.realm.compile_env.stack.len() == 1
&& self.global_bindings().contains_key(&name_str);
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
let env = self
.realm
.compile_env
@ -261,19 +237,14 @@ impl Context {
.last_mut()
.expect("global environment must always exist");
if env.bindings.contains_key(&name) || exists_global {
self.throw_syntax_error(format!("Redeclaration of variable {}", name_str))
} else {
let binding_index = env.bindings.len();
env.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
},
);
Ok(())
}
let binding_index = env.bindings.len();
env.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
},
);
}
/// Initialize an immutable binding at bytecode compile time and return it's binding locator.

5
boa_engine/src/realm.rs

@ -45,11 +45,6 @@ impl Realm {
&self.global_object
}
#[inline]
pub(crate) fn global_bindings(&self) -> &GlobalPropertyMap {
self.global_property_map.string_property_map()
}
#[inline]
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
self.global_property_map.string_property_map_mut()

10
boa_engine/src/syntax/ast/node/block/mod.rs

@ -4,7 +4,6 @@ use super::{Node, StatementList};
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString};
use rustc_hash::FxHashSet;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
@ -40,12 +39,9 @@ impl Block {
self.statements.items()
}
pub(crate) fn lexically_declared_names(&self, interner: &Interner) -> FxHashSet<Sym> {
self.statements.lexically_declared_names(interner)
}
pub(crate) fn var_declared_named(&self) -> FxHashSet<Sym> {
self.statements.var_declared_names()
/// Get the lexically declared names of the block.
pub(crate) fn lexically_declared_names(&self) -> Vec<(Sym, bool)> {
self.statements.lexically_declared_names()
}
/// Implements the display formatting with indentation.

23
boa_engine/src/syntax/ast/node/iteration/mod.rs

@ -8,7 +8,7 @@ use crate::syntax::ast::node::{
declaration::Declaration, identifier::Identifier, DeclarationPattern,
};
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, ToInternedString};
use boa_interner::{Interner, Sym, ToInternedString};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
@ -26,6 +26,27 @@ pub enum IterableLoopInitializer {
DeclarationPattern(DeclarationPattern),
}
impl IterableLoopInitializer {
/// Return the bound names of a for loop initializer.
///
/// The returned list may contain duplicates.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames
pub(crate) fn bound_names(&self) -> Vec<Sym> {
match self {
IterableLoopInitializer::Let(decl) | IterableLoopInitializer::Const(decl) => match decl
{
Declaration::Identifier { ident, .. } => vec![ident.sym()],
Declaration::Pattern(pattern) => pattern.idents(),
},
_ => Vec::new(),
}
}
}
impl ToInternedString for IterableLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {

123
boa_engine/src/syntax/ast/node/mod.rs

@ -57,7 +57,8 @@ pub(crate) use self::parameters::FormalParameterListFlags;
use super::Const;
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, ToInternedString};
use boa_interner::{Interner, Sym, ToInternedString};
use rustc_hash::FxHashSet;
use std::cmp::Ordering;
#[cfg(feature = "deser")]
@ -343,6 +344,118 @@ impl Node {
Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation),
}
}
pub(crate) fn var_declared_names(&self, vars: &mut FxHashSet<Sym>) {
match self {
Node::Block(block) => {
for node in block.items() {
node.var_declared_names(vars);
}
}
Node::VarDeclList(DeclarationList::Var(declarations)) => {
for declaration in declarations.iter() {
match declaration {
Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
vars.insert(ident);
}
}
}
}
}
Node::If(if_statement) => {
if_statement.body().var_declared_names(vars);
if let Some(node) = if_statement.else_node() {
node.var_declared_names(vars);
}
}
Node::DoWhileLoop(do_while_loop) => {
do_while_loop.body().var_declared_names(vars);
}
Node::WhileLoop(while_loop) => {
while_loop.body().var_declared_names(vars);
}
Node::ForLoop(for_loop) => {
if let Some(Node::VarDeclList(DeclarationList::Var(declarations))) = for_loop.init()
{
for declaration in declarations.iter() {
match declaration {
Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
vars.insert(ident);
}
}
}
}
}
for_loop.body().var_declared_names(vars);
}
Node::ForInLoop(for_in_loop) => {
if let iteration::IterableLoopInitializer::Var(declaration) = for_in_loop.init() {
match declaration {
Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
vars.insert(ident);
}
}
}
}
for_in_loop.body().var_declared_names(vars);
}
Node::ForOfLoop(for_of_loop) => {
if let iteration::IterableLoopInitializer::Var(declaration) = for_of_loop.init() {
match declaration {
Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
vars.insert(ident);
}
}
}
}
for_of_loop.body().var_declared_names(vars);
}
Node::Switch(switch) => {
for case in switch.cases() {
for node in case.body().items() {
node.var_declared_names(vars);
}
}
if let Some(nodes) = switch.default() {
for node in nodes {
node.var_declared_names(vars);
}
}
}
Node::Try(try_statement) => {
for node in try_statement.block().items() {
node.var_declared_names(vars);
}
if let Some(catch) = try_statement.catch() {
for node in catch.block().items() {
node.var_declared_names(vars);
}
}
if let Some(finally) = try_statement.finally() {
for node in finally.items() {
node.var_declared_names(vars);
}
}
}
_ => {}
}
}
}
impl ToInternedString for Node {
@ -380,7 +493,7 @@ where
/// level.
#[cfg(test)]
fn test_formatting(source: &'static str) {
use crate::syntax::Parser;
use crate::{syntax::Parser, Context};
// Remove preceding newline.
let source = &source[1..];
@ -395,11 +508,11 @@ fn test_formatting(source: &'static str) {
.map(|l| &l[characters_to_remove..]) // Remove preceding whitespace from each line
.collect::<Vec<&'static str>>()
.join("\n");
let mut interner = Interner::default();
let mut context = Context::default();
let result = Parser::new(scenario.as_bytes(), false)
.parse_all(&mut interner)
.parse_all(&mut context)
.expect("parsing failed")
.to_interned_string(&interner);
.to_interned_string(context.interner());
if scenario != result {
eprint!("========= Expected:\n{scenario}");
eprint!("========= Got:\n{result}");

21
boa_engine/src/syntax/ast/node/parameters.rs

@ -1,3 +1,5 @@
use crate::syntax::{ast::Position, parser::ParseError};
use super::{Declaration, DeclarationPattern, Node};
use bitflags::bitflags;
use boa_gc::{Finalize, Trace};
@ -77,6 +79,25 @@ impl FormalParameterList {
pub(crate) fn has_arguments(&self) -> bool {
self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS)
}
/// Helper to check if any parameter names are declared in the given list.
pub(crate) fn name_in_lexically_declared_names(
&self,
names: &[Sym],
position: Position,
) -> Result<(), ParseError> {
for parameter in self.parameters.iter() {
for name in &parameter.names() {
if names.contains(name) {
return Err(ParseError::General {
message: "formal parameter declared in lexically declared names",
position,
});
}
}
}
Ok(())
}
}
impl From<FormalParameter> for FormalParameterList {

134
boa_engine/src/syntax/ast/node/statement_list/mod.rs

@ -68,64 +68,110 @@ impl StatementList {
buf
}
pub fn lexically_declared_names(&self, interner: &Interner) -> FxHashSet<Sym> {
let mut set = FxHashSet::default();
for stmt in self.items() {
if let Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) = stmt {
for decl in decl_list.as_ref() {
// It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries.
// https://tc39.es/ecma262/#sec-block-static-semantics-early-errors
match decl {
Declaration::Identifier { ident, .. } => {
if !set.insert(ident.sym()) {
unreachable!(
"Redeclaration of {}",
interner.resolve_expect(ident.sym())
);
}
}
Declaration::Pattern(p) => {
for ident in p.idents().iter().copied() {
if !set.insert(ident) {
unreachable!(
"Redeclaration of {}",
interner.resolve_expect(ident)
/// Return the lexically declared names of a `StatementList`.
///
/// The returned list may contain duplicates.
///
/// If a declared name originates from a function declaration it is flagged as `true` in the returned list.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
pub(crate) fn lexically_declared_names(&self) -> Vec<(Sym, bool)> {
let mut names = Vec::new();
for node in self.items() {
match node {
Node::FunctionDecl(decl) => {
names.push((decl.name(), true));
}
Node::GeneratorDecl(decl) => {
names.push((decl.name(), false));
}
Node::AsyncFunctionDecl(decl) => {
names.push((decl.name(), false));
}
Node::AsyncGeneratorDecl(decl) => {
names.push((decl.name(), false));
}
Node::ClassDecl(decl) => {
names.push((decl.name(), false));
}
Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => match decl_list {
super::DeclarationList::Const(declarations)
| super::DeclarationList::Let(declarations) => {
for decl in declarations.iter() {
match decl {
Declaration::Identifier { ident, .. } => {
names.push((ident.sym(), false));
}
Declaration::Pattern(pattern) => {
names.extend(
pattern.idents().into_iter().map(|name| (name, false)),
);
}
}
}
}
}
super::DeclarationList::Var(_) => unreachable!(),
},
_ => {}
}
}
set
}
pub fn function_declared_names(&self) -> FxHashSet<Sym> {
let mut set = FxHashSet::default();
for stmt in self.items() {
if let Node::FunctionDecl(decl) = stmt {
set.insert(decl.name());
}
}
set
names
}
pub fn var_declared_names(&self) -> FxHashSet<Sym> {
let mut set = FxHashSet::default();
for stmt in self.items() {
if let Node::VarDeclList(decl_list) = stmt {
for decl in decl_list.as_ref() {
match decl {
Declaration::Identifier { ident, .. } => {
set.insert(ident.sym());
/// Return the top level lexically declared names of a `StatementList`.
///
/// The returned list may contain duplicates.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames
pub(crate) fn lexically_declared_names_top_level(&self) -> Vec<Sym> {
let mut names = Vec::new();
for node in self.items() {
match node {
Node::ClassDecl(decl) => {
names.push(decl.name());
}
Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => match decl_list {
super::DeclarationList::Const(declarations)
| super::DeclarationList::Let(declarations) => {
for decl in declarations.iter() {
match decl {
Declaration::Identifier { ident, .. } => {
names.push(ident.sym());
}
Declaration::Pattern(pattern) => {
names.extend(pattern.idents());
}
}
}
Declaration::Pattern(p) => set.extend(p.idents().into_iter()),
}
}
super::DeclarationList::Var(_) => unreachable!(),
},
_ => {}
}
}
set
names
}
/// Return the variable declared names of a `StatementList`.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-vardeclarednames
pub(crate) fn var_declared_names_new(&self, vars: &mut FxHashSet<Sym>) {
for node in self.items() {
node.var_declared_names(vars);
}
}
}

26
boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs

@ -14,7 +14,7 @@ use crate::syntax::{
declaration::Declaration, ArrowFunctionDecl, FormalParameter, FormalParameterList,
FormalParameterListFlags, Node, Return, StatementList,
},
Position, Punctuator,
Punctuator,
},
lexer::{Error as LexError, TokenKind},
parser::{
@ -147,26 +147,10 @@ where
// It is a Syntax Error if any element of the BoundNames of ArrowParameters
// also occurs in the LexicallyDeclaredNames of ConciseBody.
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
Ok(ArrowFunctionDecl::new(self.name, params, body))
}

27
boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs

@ -9,25 +9,26 @@ use boa_interner::{Interner, Sym};
/// Checks an empty array.
#[test]
fn check_empty() {
let mut interner = Interner::default();
check_parser("[]", vec![ArrayDecl::from(vec![]).into()], &mut interner);
check_parser(
"[]",
vec![ArrayDecl::from(vec![]).into()],
Interner::default(),
);
}
/// Checks an array with empty slot.
#[test]
fn check_empty_slot() {
let mut interner = Interner::default();
check_parser(
"[,]",
vec![ArrayDecl::from(vec![Node::Empty]).into()],
&mut interner,
Interner::default(),
);
}
/// Checks a numeric array.
#[test]
fn check_numeric_array() {
let mut interner = Interner::default();
check_parser(
"[1, 2, 3]",
vec![ArrayDecl::from(vec![
@ -36,14 +37,13 @@ fn check_numeric_array() {
Const::from(3).into(),
])
.into()],
&mut interner,
Interner::default(),
);
}
// Checks a numeric array with trailing comma
#[test]
fn check_numeric_array_trailing() {
let mut interner = Interner::default();
check_parser(
"[1, 2, 3,]",
vec![ArrayDecl::from(vec![
@ -52,14 +52,13 @@ fn check_numeric_array_trailing() {
Const::from(3).into(),
])
.into()],
&mut interner,
Interner::default(),
);
}
/// Checks a numeric array with an elision.
#[test]
fn check_numeric_array_elision() {
let mut interner = Interner::default();
check_parser(
"[1, 2, , 3]",
vec![ArrayDecl::from(vec![
@ -69,14 +68,13 @@ fn check_numeric_array_elision() {
Const::from(3).into(),
])
.into()],
&mut interner,
Interner::default(),
);
}
/// Checks a numeric array with repeated elisions.
#[test]
fn check_numeric_array_repeated_elision() {
let mut interner = Interner::default();
check_parser(
"[1, 2, ,, 3]",
vec![ArrayDecl::from(vec![
@ -87,7 +85,7 @@ fn check_numeric_array_repeated_elision() {
Const::from(3).into(),
])
.into()],
&mut interner,
Interner::default(),
);
}
@ -103,14 +101,13 @@ fn check_combined() {
Const::from(2).into(),
])
.into()],
&mut interner,
interner,
);
}
/// Checks a combined array with an empty string
#[test]
fn check_combined_empty_str() {
let mut interner = Interner::default();
check_parser(
"[1, \"\", 2]",
vec![ArrayDecl::from(vec![
@ -119,6 +116,6 @@ fn check_combined_empty_str() {
Const::from(2).into(),
])
.into()],
&mut interner,
Interner::default(),
);
}

24
boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs

@ -127,26 +127,10 @@ where
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
Ok(AsyncFunctionExpr::new(name, params, body))
}

4
boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs

@ -35,7 +35,7 @@ fn check_async_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -86,6 +86,6 @@ fn check_nested_async_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

24
boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs

@ -143,26 +143,10 @@ where
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
//implement the below AsyncGeneratorExpr in ast::node
Ok(AsyncGeneratorExpr::new(name, params, body))

4
boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs

@ -36,7 +36,7 @@ fn check_async_generator_expr() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -87,6 +87,6 @@ fn check_nested_async_generator_expr() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

24
boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs

@ -119,26 +119,10 @@ where
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
Ok(FunctionExpr::new(name, params, body))
}

4
boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs

@ -34,7 +34,7 @@ fn check_function_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -85,6 +85,6 @@ fn check_nested_function_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

24
boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs

@ -123,26 +123,10 @@ where
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
Ok(GeneratorExpr::new(name, params, body))
}

4
boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs

@ -33,7 +33,7 @@ fn check_generator_function_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -61,6 +61,6 @@ fn check_generator_function_delegate_yield_expression() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

93
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -17,7 +17,7 @@ use crate::syntax::{
AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr,
GeneratorExpr, Identifier, Node, Object,
},
Const, Keyword, Position, Punctuator,
Const, Keyword, Punctuator,
},
lexer::{token::Numeric, Error as LexError, TokenKind},
parser::{
@ -411,6 +411,25 @@ where
)));
}
// It is a Syntax Error if any element of the BoundNames of FormalParameters also occurs in the LexicallyDeclaredNames of FunctionBody.
let lexically_declared_names = body.lexically_declared_names();
for parameter in params.parameters.iter() {
for name in &parameter.names() {
if lexically_declared_names.contains(&(*name, false)) {
return Err(ParseError::general(
"formal parameter declared in lexically declared names",
params_start_position,
));
}
if lexically_declared_names.contains(&(*name, true)) {
return Err(ParseError::general(
"formal parameter declared in lexically declared names",
params_start_position,
));
}
}
}
Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)),
property_name,
@ -614,26 +633,10 @@ where
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
body_start,
)?;
Ok((
property_name,
@ -717,26 +720,10 @@ where
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
body_start,
)?;
Ok((
property_name,
@ -815,26 +802,10 @@ where
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
body_start,
)?;
Ok((
property_name,

24
boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -35,7 +35,7 @@ fn check_object_literal() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -70,7 +70,7 @@ fn check_object_short_function() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -115,7 +115,7 @@ fn check_object_short_function_arguments() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -149,7 +149,7 @@ fn check_object_getter() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -193,7 +193,7 @@ fn check_object_setter() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -223,7 +223,7 @@ fn check_object_short_function_get() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -253,7 +253,7 @@ fn check_object_short_function_set() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -288,7 +288,7 @@ fn check_object_shorthand_property_names() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -338,7 +338,7 @@ fn check_object_shorthand_multiple_properties() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -362,7 +362,7 @@ fn check_object_spread() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -392,7 +392,7 @@ fn check_async_method() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -422,7 +422,7 @@ fn check_async_generator_method() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

5
boa_engine/src/syntax/parser/expression/primary/tests.rs

@ -4,11 +4,10 @@ use boa_interner::{Interner, Sym};
#[test]
fn check_string() {
// Check empty string
let mut interner = Interner::default();
check_parser(
"\"\"",
vec![Const::from(Sym::EMPTY_STRING).into()],
&mut interner,
Interner::default(),
);
// Check non-empty string
@ -16,6 +15,6 @@ fn check_string() {
check_parser(
"\"hello\"",
vec![Const::from(interner.get_or_intern_static("hello")).into()],
&mut interner,
interner,
);
}

88
boa_engine/src/syntax/parser/expression/tests.rs

@ -20,7 +20,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -32,7 +32,7 @@ fn check_numeric_operations() {
Const::from(1),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -44,7 +44,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -56,7 +56,7 @@ fn check_numeric_operations() {
Const::from(1),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -68,7 +68,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -80,7 +80,7 @@ fn check_numeric_operations() {
Const::from(2),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -92,7 +92,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -104,7 +104,7 @@ fn check_numeric_operations() {
Const::from(2),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -116,7 +116,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -128,7 +128,7 @@ fn check_numeric_operations() {
Const::from(2),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -140,7 +140,7 @@ fn check_numeric_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -152,7 +152,7 @@ fn check_numeric_operations() {
Const::from(2),
)
.into()],
&mut interner,
interner,
);
}
@ -180,7 +180,7 @@ fn check_complex_numeric_operations() {
Const::from(1),
)
.into()],
&mut interner,
interner,
);
}
@ -196,7 +196,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -208,7 +208,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -220,7 +220,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -232,7 +232,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -244,7 +244,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -256,7 +256,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -268,7 +268,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -280,7 +280,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -292,7 +292,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -304,7 +304,7 @@ fn check_bitwise_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
}
@ -320,7 +320,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -332,7 +332,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -344,7 +344,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -356,7 +356,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -368,7 +368,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -380,7 +380,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -392,7 +392,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -404,7 +404,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -416,7 +416,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -428,7 +428,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -440,7 +440,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -452,7 +452,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -464,7 +464,7 @@ fn check_assign_operations() {
BinOp::new(NumOp::Div, Const::from(10), Const::from(2)),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -476,7 +476,7 @@ fn check_assign_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
}
@ -491,7 +491,7 @@ fn check_relational_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -503,7 +503,7 @@ fn check_relational_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -515,7 +515,7 @@ fn check_relational_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -527,7 +527,7 @@ fn check_relational_operations() {
Identifier::new(interner.get_or_intern_static("b")),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -539,7 +539,7 @@ fn check_relational_operations() {
Identifier::new(interner.get_or_intern_static("o")),
)
.into()],
&mut interner,
interner,
);
}
@ -566,7 +566,7 @@ fn check_logical_expressions() {
),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -582,7 +582,7 @@ fn check_logical_expressions() {
Identifier::new(interner.get_or_intern_static("c")),
)
.into()],
&mut interner,
interner,
);
check_invalid("a ?? b && c");

59
boa_engine/src/syntax/parser/function/tests.rs

@ -1,10 +1,13 @@
use crate::syntax::{
ast::node::{
ArrowFunctionDecl, BinOp, Declaration, DeclarationList, FormalParameter,
FormalParameterList, FunctionDecl, Identifier, Node, Return,
use crate::{
syntax::{
ast::node::{
ArrowFunctionDecl, BinOp, Declaration, DeclarationList, FormalParameter,
FormalParameterList, FunctionDecl, Identifier, Node, Return,
},
ast::{node::FormalParameterListFlags, op::NumOp},
parser::{tests::check_parser, Parser},
},
ast::{node::FormalParameterListFlags, op::NumOp},
parser::{tests::check_parser, Parser},
Context,
};
use boa_interner::Interner;
@ -27,7 +30,7 @@ fn check_basic() {
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -57,7 +60,7 @@ fn check_duplicates_strict_off() {
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -65,9 +68,9 @@ fn check_duplicates_strict_off() {
#[test]
fn check_duplicates_strict_on() {
let js = "'use strict'; function foo(a, a) {}";
let mut interner = Interner::default();
let mut context = Context::default();
let res = Parser::new(js.as_bytes(), false).parse_all(&mut interner);
let res = Parser::new(js.as_bytes(), false).parse_all(&mut context);
dbg!(&res);
assert!(res.is_err());
}
@ -91,7 +94,7 @@ fn check_basic_semicolon_insertion() {
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -114,7 +117,7 @@ fn check_empty_return() {
vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -137,7 +140,7 @@ fn check_empty_return_semicolon_insertion() {
vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -167,7 +170,7 @@ fn check_rest_operator() {
vec![],
)
.into()],
&mut interner,
interner,
);
}
@ -191,7 +194,7 @@ fn check_arrow_only_rest() {
vec![],
)
.into()],
&mut interner,
interner,
);
}
@ -225,7 +228,7 @@ fn check_arrow_rest() {
vec![],
)
.into()],
&mut interner,
interner,
);
}
@ -262,7 +265,7 @@ fn check_arrow() {
.into()],
)
.into()],
&mut interner,
interner,
);
}
@ -299,7 +302,7 @@ fn check_arrow_semicolon_insertion() {
.into()],
)
.into()],
&mut interner,
interner,
);
}
@ -328,7 +331,7 @@ fn check_arrow_epty_return() {
vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -357,7 +360,7 @@ fn check_arrow_empty_return_semicolon_insertion() {
vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -395,7 +398,7 @@ fn check_arrow_assignment() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -433,7 +436,7 @@ fn check_arrow_assignment_nobrackets() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -471,7 +474,7 @@ fn check_arrow_assignment_noparenthesis() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -509,7 +512,7 @@ fn check_arrow_assignment_noparenthesis_nobrackets() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -556,7 +559,7 @@ fn check_arrow_assignment_2arg() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -603,7 +606,7 @@ fn check_arrow_assignment_2arg_nobrackets() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -657,7 +660,7 @@ fn check_arrow_assignment_3arg() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -711,6 +714,6 @@ fn check_arrow_assignment_3arg_nobrackets() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

78
boa_engine/src/syntax/parser/mod.rs

@ -11,10 +11,16 @@ mod tests;
pub use self::error::{ParseError, ParseResult};
use self::cursor::Cursor;
use crate::syntax::{ast::node::StatementList, lexer::TokenKind};
use boa_interner::Interner;
use crate::{
syntax::{ast::node::StatementList, lexer::TokenKind},
Context,
};
use boa_interner::{Interner, Sym};
use rustc_hash::{FxHashMap, FxHashSet};
use std::io::Read;
use super::ast::Position;
/// Trait implemented by parsers.
///
/// This makes it possible to abstract over the underlying implementation of a parser.
@ -92,6 +98,7 @@ pub struct Parser<R> {
}
impl<R> Parser<R> {
/// Create a new `Parser` with a reader as the input to parse.
pub fn new(reader: R, strict_mode: bool) -> Self
where
R: Read,
@ -102,11 +109,74 @@ impl<R> Parser<R> {
Self { cursor }
}
pub fn parse_all(&mut self, interner: &mut Interner) -> Result<StatementList, ParseError>
/// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation.
/// The resulting `StatementList` can be compiled into boa bytecode and executed in the boa vm.
///
/// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_all(&mut self, context: &mut Context) -> Result<StatementList, ParseError>
where
R: Read,
{
Script.parse(&mut self.cursor, interner)
let statement_list = Script.parse(&mut self.cursor, context.interner_mut())?;
// It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
let mut var_declared_names = FxHashSet::default();
statement_list.var_declared_names_new(&mut var_declared_names);
let lexically_declared_names = statement_list.lexically_declared_names();
let mut lexically_declared_names_map: FxHashMap<Sym, bool> = FxHashMap::default();
for (name, is_function_declaration) in &lexically_declared_names {
if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) {
if !(*is_function_declaration && *existing_is_function_declaration) {
return Err(ParseError::general(
"lexical name declared multiple times",
Position::new(1, 1),
));
}
}
lexically_declared_names_map.insert(*name, *is_function_declaration);
if !is_function_declaration && var_declared_names.contains(name) {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
}
if context.has_binding(*name) {
return Err(ParseError::general(
"lexical name declared multiple times",
Position::new(1, 1),
));
}
if !is_function_declaration {
let name_str = context.interner().resolve_expect(*name);
let desc = context
.realm
.global_property_map
.string_property_map()
.get(name_str);
let non_configurable_binding_exists = match desc {
Some(desc) => !matches!(desc.configurable(), Some(true)),
None => false,
};
if non_configurable_binding_exists {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
}
}
}
for name in var_declared_names {
if context.has_binding(name) {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
}
}
Ok(statement_list)
}
}

39
boa_engine/src/syntax/parser/statement/block/mod.rs

@ -16,8 +16,9 @@ use crate::syntax::{
lexer::TokenKind,
parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser},
};
use boa_interner::Interner;
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use rustc_hash::{FxHashMap, FxHashSet};
use std::io::Read;
/// The possible `TokenKind` which indicate the end of a block statement.
@ -81,7 +82,11 @@ where
return Ok(node::Block::from(vec![]));
}
}
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let statement_list = StatementList::new(
self.allow_yield,
self.allow_await,
@ -93,6 +98,36 @@ where
.map(node::Block::from)?;
cursor.expect(Punctuator::CloseBlock, "block", interner)?;
let lexically_declared_names = statement_list.lexically_declared_names();
let mut lexically_declared_names_map: FxHashMap<Sym, bool> = FxHashMap::default();
for (name, is_function_declaration) in &lexically_declared_names {
if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) {
if !(!cursor.strict_mode()
&& *is_function_declaration
&& *existing_is_function_declaration)
{
return Err(ParseError::general(
"lexical name declared multiple times",
position,
));
}
}
lexically_declared_names_map.insert(*name, *is_function_declaration);
}
let mut var_declared_names = FxHashSet::default();
for node in statement_list.items() {
node.var_declared_names(&mut var_declared_names);
}
for (lex_name, _) in &lexically_declared_names {
if var_declared_names.contains(lex_name) {
return Err(ParseError::general(
"lexical name declared in var names",
position,
));
}
}
Ok(statement_list)
}
}

15
boa_engine/src/syntax/parser/statement/block/tests.rs

@ -13,8 +13,8 @@ use crate::syntax::{
use boa_interner::Interner;
/// Helper function to check a block.
// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809
fn check_block<B>(js: &str, block: B, interner: &mut Interner)
#[track_caller]
fn check_block<B>(js: &str, block: B, interner: Interner)
where
B: Into<Box<[Node]>>,
{
@ -23,8 +23,7 @@ where
#[test]
fn empty() {
let mut interner = Interner::default();
check_block("{}", vec![], &mut interner);
check_block("{}", vec![], Interner::default());
}
#[test]
@ -47,7 +46,7 @@ fn non_empty() {
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -79,7 +78,7 @@ fn non_empty() {
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
],
&mut interner,
interner,
);
}
@ -112,7 +111,7 @@ fn hoisting() {
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -129,6 +128,6 @@ fn hoisting() {
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
DeclarationList::Var(vec![Declaration::new_with_identifier(a, None)].into()).into(),
],
&mut interner,
interner,
);
}

26
boa_engine/src/syntax/parser/statement/break_stm/tests.rs

@ -9,28 +9,25 @@ use boa_interner::Interner;
#[test]
fn inline() {
let mut interner = Interner::default();
check_parser(
"while (true) break;",
vec![WhileLoop::new(Const::from(true), Node::Break(Break::new(None))).into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn new_line() {
let mut interner = Interner::default();
check_parser(
"while (true)
break;",
vec![WhileLoop::new(Const::from(true), Break::new(None)).into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn inline_block_semicolon_insertion() {
let mut interner = Interner::default();
check_parser(
"while (true) {break}",
vec![WhileLoop::new(
@ -38,7 +35,7 @@ fn inline_block_semicolon_insertion() {
Block::from(vec![Break::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
@ -56,13 +53,12 @@ fn new_line_semicolon_insertion() {
]),
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn inline_block() {
let mut interner = Interner::default();
check_parser(
"while (true) {break;}",
vec![WhileLoop::new(
@ -70,7 +66,7 @@ fn inline_block() {
Block::from(vec![Break::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
@ -88,7 +84,7 @@ fn new_line_block() {
]),
)
.into()],
&mut interner,
interner,
);
}
@ -106,7 +102,7 @@ fn reserved_label() {
]),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -121,13 +117,12 @@ fn reserved_label() {
]),
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn new_line_block_empty() {
let mut interner = Interner::default();
check_parser(
"while (true) {
break;
@ -137,13 +132,12 @@ fn new_line_block_empty() {
Block::from(vec![Break::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn new_line_block_empty_semicolon_insertion() {
let mut interner = Interner::default();
check_parser(
"while (true) {
break
@ -153,6 +147,6 @@ fn new_line_block_empty_semicolon_insertion() {
Block::from(vec![Break::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}

26
boa_engine/src/syntax/parser/statement/continue_stm/tests.rs

@ -9,28 +9,25 @@ use boa_interner::Interner;
#[test]
fn inline() {
let mut interner = Interner::default();
check_parser(
"while (true) continue;",
vec![WhileLoop::new(Const::from(true), Continue::new(None)).into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn new_line() {
let mut interner = Interner::default();
check_parser(
"while (true)
continue;",
vec![WhileLoop::new(Const::from(true), Continue::new(None)).into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn inline_block_semicolon_insertion() {
let mut interner = Interner::default();
check_parser(
"while (true) {continue}",
vec![WhileLoop::new(
@ -38,7 +35,7 @@ fn inline_block_semicolon_insertion() {
Block::from(vec![Continue::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
@ -56,13 +53,12 @@ fn new_line_semicolon_insertion() {
]),
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn inline_block() {
let mut interner = Interner::default();
check_parser(
"while (true) {continue;}",
vec![WhileLoop::new(
@ -70,7 +66,7 @@ fn inline_block() {
Block::from(vec![Continue::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
@ -88,7 +84,7 @@ fn new_line_block() {
]),
)
.into()],
&mut interner,
interner,
);
}
@ -106,7 +102,7 @@ fn reserved_label() {
]),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -121,13 +117,12 @@ fn reserved_label() {
]),
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn new_line_block_empty() {
let mut interner = Interner::default();
check_parser(
"while (true) {
continue;
@ -137,13 +132,12 @@ fn new_line_block_empty() {
Block::from(vec![Continue::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn new_line_block_empty_semicolon_insertion() {
let mut interner = Interner::default();
check_parser(
"while (true) {
continue
@ -153,6 +147,6 @@ fn new_line_block_empty_semicolon_insertion() {
Block::from(vec![Continue::new(None).into()]),
)
.into()],
&mut interner,
Interner::default(),
);
}

6
boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs

@ -16,7 +16,7 @@ fn async_function_declaration() {
vec![],
)
.into()],
&mut interner,
interner,
);
}
@ -32,7 +32,7 @@ fn async_function_declaration_keywords() {
vec![],
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -44,6 +44,6 @@ fn async_function_declaration_keywords() {
vec![],
)
.into()],
&mut interner,
interner,
);
}

2
boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs

@ -15,6 +15,6 @@ fn async_generator_function_declaration() {
vec![],
)
.into()],
&mut interner,
interner,
);
}

39
boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs

@ -24,7 +24,7 @@ use crate::syntax::{
};
use boa_interner::{Interner, Sym};
use node::Node;
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use std::io::Read;
/// Class declaration parsing.
@ -555,9 +555,46 @@ where
} else {
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let statement_list =
StatementList::new(false, true, false, true, &FUNCTION_BREAK_TOKENS)
.parse(cursor, interner)?;
let lexically_declared_names = statement_list.lexically_declared_names();
let mut lexically_declared_names_map: FxHashMap<Sym, bool> =
FxHashMap::default();
for (name, is_function_declaration) in &lexically_declared_names {
if let Some(existing_is_function_declaration) =
lexically_declared_names_map.get(name)
{
if !(!cursor.strict_mode()
&& *is_function_declaration
&& *existing_is_function_declaration)
{
return Err(ParseError::general(
"lexical name declared multiple times",
position,
));
}
}
lexically_declared_names_map.insert(*name, *is_function_declaration);
}
let mut var_declared_names = FxHashSet::default();
statement_list.var_declared_names_new(&mut var_declared_names);
for (lex_name, _) in &lexically_declared_names {
if var_declared_names.contains(lex_name) {
return Err(ParseError::general(
"lexical name declared in var names",
position,
));
}
}
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"class definition",

6
boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs

@ -16,7 +16,7 @@ fn function_declaration() {
vec![],
)
.into()],
&mut interner,
interner,
);
}
@ -32,7 +32,7 @@ fn function_declaration_keywords() {
vec![],
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -44,6 +44,6 @@ fn function_declaration_keywords() {
vec![],
)
.into()],
&mut interner,
interner,
);
}

2
boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs

@ -15,6 +15,6 @@ fn generator_function_declaration() {
vec![],
)
.into()],
&mut interner,
interner,
);
}

24
boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs

@ -207,26 +207,10 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
params.name_in_lexically_declared_names(
&body.lexically_declared_names_top_level(),
params_start_position,
)?;
Ok((name, params, body))
}

34
boa_engine/src/syntax/parser/statement/declaration/tests.rs

@ -21,7 +21,7 @@ fn var_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -39,7 +39,7 @@ fn var_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -53,7 +53,7 @@ fn var_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -71,7 +71,7 @@ fn var_declaration_no_spaces() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -89,7 +89,7 @@ fn empty_var_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -114,7 +114,7 @@ fn multiple_var_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -132,7 +132,7 @@ fn let_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -150,7 +150,7 @@ fn let_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -164,7 +164,7 @@ fn let_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -182,7 +182,7 @@ fn let_declaration_no_spaces() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -200,7 +200,7 @@ fn empty_let_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -225,7 +225,7 @@ fn multiple_let_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -243,7 +243,7 @@ fn const_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -261,7 +261,7 @@ fn const_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -275,7 +275,7 @@ fn const_declaration_keywords() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -293,7 +293,7 @@ fn const_declaration_no_spaces() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -323,6 +323,6 @@ fn multiple_const_declaration() {
.into(),
)
.into()],
&mut interner,
interner,
);
}

6
boa_engine/src/syntax/parser/statement/if_stm/tests.rs

@ -9,20 +9,18 @@ use boa_interner::Interner;
#[test]
fn if_without_else_block() {
let mut interner = Interner::default();
check_parser(
"if (true) {}",
vec![If::new::<_, _, Node, _>(Const::from(true), Block::from(Vec::new()), None).into()],
&mut interner,
Interner::default(),
);
}
#[test]
fn if_without_else_block_with_trailing_newline() {
let mut interner = Interner::default();
check_parser(
"if (true) {}\n",
vec![If::new::<_, _, Node, _>(Const::from(true), Block::from(Vec::new()), None).into()],
&mut interner,
Interner::default(),
);
}

58
boa_engine/src/syntax/parser/statement/iteration/for_statement.rs

@ -26,8 +26,9 @@ use crate::syntax::{
AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser,
},
};
use boa_interner::Interner;
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use rustc_hash::FxHashSet;
use std::io::Read;
/// For statement parsing
@ -116,6 +117,7 @@ where
));
}
(Some(init), TokenKind::Keyword((Keyword::In, false))) => {
let init_position = token.span().start();
let init = node_to_iterable_loop_initializer(init, init_position)?;
let _next = cursor.next(interner)?;
@ -135,6 +137,33 @@ where
return Err(ParseError::wrong_function_declaration_non_strict(position));
}
// It is a Syntax Error if the BoundNames of ForDeclaration contains "let".
// It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement.
// It is a Syntax Error if the BoundNames of ForDeclaration contains any duplicate entries.
let mut vars = FxHashSet::default();
body.var_declared_names(&mut vars);
let mut bound_names = FxHashSet::default();
for name in init.bound_names() {
if name == Sym::LET {
return Err(ParseError::general(
"Cannot use 'let' as a lexically bound name",
init_position,
));
}
if vars.contains(&name) {
return Err(ParseError::general(
"For loop initializer declared in loop body",
init_position,
));
}
if !bound_names.insert(name) {
return Err(ParseError::general(
"For loop initializer cannot contain duplicate identifiers",
init_position,
));
}
}
return Ok(ForInLoop::new(init, expr, body).into());
}
(Some(init), TokenKind::Keyword((Keyword::Of, false))) => {
@ -157,6 +186,33 @@ where
return Err(ParseError::wrong_function_declaration_non_strict(position));
}
// It is a Syntax Error if the BoundNames of ForDeclaration contains "let".
// It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement.
// It is a Syntax Error if the BoundNames of ForDeclaration contains any duplicate entries.
let mut vars = FxHashSet::default();
body.var_declared_names(&mut vars);
let mut bound_names = FxHashSet::default();
for name in init.bound_names() {
if name == Sym::LET {
return Err(ParseError::general(
"Cannot use 'let' as a lexically bound name",
init_position,
));
}
if vars.contains(&name) {
return Err(ParseError::general(
"For loop initializer declared in loop body",
init_position,
));
}
if !bound_names.insert(name) {
return Err(ParseError::general(
"For loop initializer cannot contain duplicate identifiers",
init_position,
));
}
}
return Ok(ForOfLoop::new(init, iterable, body).into());
}
(Some(Node::ConstDeclList(list)), _) => {

12
boa_engine/src/syntax/parser/statement/iteration/tests.rs

@ -29,7 +29,7 @@ fn check_do_while() {
Const::from(true),
)
.into()],
&mut interner,
interner,
);
}
@ -77,7 +77,7 @@ fn check_do_while_semicolon_insertion() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -126,14 +126,13 @@ fn check_do_while_semicolon_insertion_no_space() {
)
.into(),
],
&mut interner,
interner,
);
}
/// Checks parsing of a while statement which is seperated out with line terminators.
#[test]
fn while_spaces() {
let mut interner = Interner::default();
check_parser(
r#"
@ -149,14 +148,13 @@ fn while_spaces() {
"#,
vec![WhileLoop::new(Const::from(true), Break::new(None)).into()],
&mut interner,
Interner::default(),
);
}
/// Checks parsing of a while statement which is seperated out with line terminators.
#[test]
fn do_while_spaces() {
let mut interner = Interner::default();
check_parser(
r#"
@ -176,7 +174,7 @@ fn do_while_spaces() {
Const::Bool(true),
)
.into()],
&mut interner,
Interner::default(),
);
}

106
boa_engine/src/syntax/parser/statement/mod.rs

@ -49,14 +49,14 @@ use crate::syntax::{
DeclarationPatternArray, DeclarationPatternObject,
},
},
Keyword, Node, Position, Punctuator,
Keyword, Node, Punctuator,
},
lexer::{Error as LexError, InputElement, Token, TokenKind},
parser::expression::{await_expr::AwaitExpression, Initializer},
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::{collections::HashSet, io::Read, vec};
use std::{io::Read, vec};
pub(in crate::syntax::parser) use declaration::ClassTail;
pub(in crate::syntax) use declaration::PrivateElement;
@ -312,108 +312,6 @@ where
while cursor.next_if(Punctuator::Semicolon, interner)?.is_some() {}
}
// Handle any redeclarations
// https://tc39.es/ecma262/#sec-block-static-semantics-early-errors
{
let mut lexically_declared_names: HashSet<Sym> = HashSet::new();
let mut var_declared_names: HashSet<Sym> = HashSet::new();
// TODO: Use more helpful positions in errors when spans are added to Nodes
for item in &items {
match item {
Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => {
for decl in decl_list.as_ref() {
// if name in VarDeclaredNames or can't be added to
// LexicallyDeclaredNames, raise an error
match decl {
node::Declaration::Identifier { ident, .. } => {
if var_declared_names.contains(&ident.sym())
|| !lexically_declared_names.insert(ident.sym())
{
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of variable `{}`",
interner.resolve_expect(ident.sym())
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
node::Declaration::Pattern(p) => {
for ident in p.idents() {
if var_declared_names.contains(&ident)
|| !lexically_declared_names.insert(ident)
{
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of variable `{}`",
interner.resolve_expect(ident)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
}
}
Node::VarDeclList(decl_list) => {
for decl in decl_list.as_ref() {
match decl {
node::Declaration::Identifier { ident, .. } => {
// if name in LexicallyDeclaredNames, raise an error
if lexically_declared_names.contains(&ident.sym()) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of variable `{}`",
interner.resolve_expect(ident.sym())
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
// otherwise, add to VarDeclaredNames
var_declared_names.insert(ident.sym());
}
node::Declaration::Pattern(p) => {
for ident in p.idents() {
// if name in LexicallyDeclaredNames, raise an error
if lexically_declared_names.contains(&ident) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of variable `{}`",
interner.resolve_expect(ident)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
// otherwise, add to VarDeclaredNames
var_declared_names.insert(ident);
}
}
}
}
}
_ => (),
}
}
}
items.sort_by(Node::hoistable_order);
Ok(items.into())

48
boa_engine/src/syntax/parser/statement/switch/mod.rs

@ -9,8 +9,9 @@ use crate::syntax::{
Cursor, ParseError, TokenParser,
},
};
use boa_interner::Interner;
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use rustc_hash::{FxHashMap, FxHashSet};
use std::io::Read;
/// The possible `TokenKind` which indicate the end of a case statement.
@ -124,6 +125,11 @@ where
let mut cases = Vec::new();
let mut default = None;
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
loop {
let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() {
@ -186,6 +192,46 @@ where
}
}
// It is a Syntax Error if the LexicallyDeclaredNames of CaseBlock contains any duplicate entries.
// It is a Syntax Error if any element of the LexicallyDeclaredNames of CaseBlock also occurs in the VarDeclaredNames of CaseBlock.
let mut lexically_declared_names = Vec::new();
let mut var_declared_names = FxHashSet::default();
for case in &cases {
lexically_declared_names.extend(case.body().lexically_declared_names());
case.body().var_declared_names_new(&mut var_declared_names);
}
if let Some(default_clause) = &default {
lexically_declared_names.extend(default_clause.lexically_declared_names());
default_clause.var_declared_names_new(&mut var_declared_names);
}
let mut lexically_declared_names_map: FxHashMap<Sym, bool> = FxHashMap::default();
for (name, is_function_declaration) in &lexically_declared_names {
if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) {
if !(!cursor.strict_mode()
&& *is_function_declaration
&& *existing_is_function_declaration)
{
return Err(ParseError::general(
"lexical name declared multiple times",
position,
));
}
}
lexically_declared_names_map.insert(*name, *is_function_declaration);
}
for (name, _) in &lexically_declared_names {
if var_declared_names.contains(name) {
return Err(ParseError::general(
"lexical name declared in var declared names",
position,
));
}
}
Ok((cases.into_boxed_slice(), default))
}
}

2
boa_engine/src/syntax/parser/statement/switch/tests.rs

@ -201,6 +201,6 @@ fn check_seperated_switch() {
)
.into(),
],
&mut interner,
interner,
);
}

2
boa_engine/src/syntax/parser/statement/throw/tests.rs

@ -10,6 +10,6 @@ fn check_throw_parsing() {
check_parser(
"throw 'error';",
vec![Throw::new(Const::from(interner.get_or_intern_static("error"))).into()],
&mut interner,
interner,
);
}

100
boa_engine/src/syntax/parser/statement/try_stm/catch.rs

@ -1,7 +1,7 @@
use crate::syntax::{
ast::{
node::{self, Identifier},
Keyword, Position, Punctuator,
Keyword, Punctuator,
},
lexer::TokenKind,
parser::{
@ -58,6 +58,11 @@ where
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("Catch", "Parsing");
cursor.expect((Keyword::Catch, false), "try statement", interner)?;
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let catch_param = if cursor.next_if(Punctuator::OpenParen, interner)?.is_some() {
let catch_param =
CatchParameter::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
@ -68,52 +73,75 @@ where
None
};
let mut set = FxHashSet::default();
let idents = match &catch_param {
Some(node::Declaration::Identifier { ident, .. }) => vec![ident.sym()],
Some(node::Declaration::Pattern(p)) => p.idents(),
_ => vec![],
};
// It is a Syntax Error if BoundNames of CatchParameter contains any duplicate elements.
// https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks
for ident in idents {
if !set.insert(ident) {
// FIXME: pass correct position once #1295 lands
return Err(ParseError::general(
"duplicate identifier",
Position::new(1, 1),
));
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
if let Some(node::Declaration::Pattern(pattern)) = &catch_param {
let mut set = FxHashSet::default();
for ident in pattern.idents() {
if !set.insert(ident) {
return Err(ParseError::general(
"duplicate catch parameter identifier",
position,
));
}
}
}
// Catch block
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let catch_block = Block::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor, interner)?;
// It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the LexicallyDeclaredNames of Block.
// It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block.
// It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block unless CatchParameter is CatchParameter : BindingIdentifier .
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
// FIXME: `lexically_declared_names` only holds part of LexicallyDeclaredNames of the
// Block e.g. function names are *not* included but should be.
let lexically_declared_names = catch_block.lexically_declared_names(interner);
let var_declared_names = catch_block.var_declared_named();
for ident in set {
// FIXME: pass correct position once #1295 lands
if lexically_declared_names.contains(&ident) {
return Err(ParseError::general(
"identifier redeclared",
Position::new(1, 1),
));
// https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks
let lexically_declared_names = catch_block.lexically_declared_names();
match &catch_param {
Some(node::Declaration::Identifier { ident, .. }) => {
if lexically_declared_names.contains(&(ident.sym(), false)) {
return Err(ParseError::general(
"catch parameter identifier declared in catch body",
position,
));
}
if lexically_declared_names.contains(&(ident.sym(), true)) {
return Err(ParseError::general(
"catch parameter identifier declared in catch body",
position,
));
}
}
if var_declared_names.contains(&ident) {
return Err(ParseError::general(
"identifier redeclared",
Position::new(1, 1),
));
Some(node::Declaration::Pattern(pattern)) => {
let mut var_declared_names = FxHashSet::default();
for node in catch_block.items() {
node.var_declared_names(&mut var_declared_names);
}
for ident in pattern.idents() {
if lexically_declared_names.contains(&(ident, false)) {
return Err(ParseError::general(
"catch parameter identifier declared in catch body",
position,
));
}
if lexically_declared_names.contains(&(ident, true)) {
return Err(ParseError::general(
"catch parameter identifier declared in catch body",
position,
));
}
if var_declared_names.contains(&ident) {
return Err(ParseError::general(
"catch parameter identifier declared in catch body",
position,
));
}
}
}
_ => {}
}
let catch_node = node::Catch::new::<_, node::Declaration, _>(catch_param, catch_block);

54
boa_engine/src/syntax/parser/statement/try_stm/tests.rs

@ -3,7 +3,7 @@ use crate::syntax::{
node::{
declaration::{BindingPatternTypeArray, BindingPatternTypeObject},
object::PropertyName,
Block, Catch, Declaration, DeclarationList, Finally, Try,
Block, Catch, Declaration, DeclarationList, Finally, Identifier, Try,
},
Const,
},
@ -25,7 +25,7 @@ fn check_inline_with_empty_try_catch() {
None,
)
.into()],
&mut interner,
interner,
);
}
@ -50,7 +50,7 @@ fn check_inline_with_var_decl_inside_try() {
None,
)
.into()],
&mut interner,
interner,
);
}
@ -82,7 +82,7 @@ fn check_inline_with_var_decl_inside_catch() {
None,
)
.into()],
&mut interner,
interner,
);
}
@ -100,17 +100,16 @@ fn check_inline_with_empty_try_catch_finally() {
Some(Finally::from(vec![])),
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn check_inline_with_empty_try_finally() {
let mut interner = Interner::default();
check_parser(
"try {} finally {}",
vec![Try::new(vec![], None, Some(Finally::from(vec![]))).into()],
&mut interner,
Interner::default(),
);
}
@ -132,7 +131,7 @@ fn check_inline_with_empty_try_var_decl_in_finally() {
.into()])),
)
.into()],
&mut interner,
interner,
);
}
@ -157,7 +156,7 @@ fn check_inline_empty_try_paramless_catch() {
None,
)
.into()],
&mut interner,
interner,
);
}
@ -192,7 +191,7 @@ fn check_inline_with_binding_pattern_object() {
None,
)
.into()],
&mut interner,
interner,
);
}
@ -222,7 +221,35 @@ fn check_inline_with_binding_pattern_array() {
None,
)
.into()],
&mut interner,
interner,
);
}
#[test]
fn check_catch_with_var_redeclaration() {
let mut interner = Interner::default();
check_parser(
"try {} catch(e) { var e = 'oh' }",
vec![Try::new(
Block::from(vec![]),
Some(Catch::new::<_, Declaration, _>(
Some(Declaration::new_with_identifier(
Identifier::new(interner.get_or_intern_static("e")),
None,
)),
vec![DeclarationList::Var(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("e"),
Some(Const::from(interner.get_or_intern_static("oh")).into()),
)]
.into(),
)
.into()],
)),
None,
)
.into()],
interner,
);
}
@ -260,8 +287,3 @@ fn check_invalid_catch_with_duplicate_params() {
fn check_invalid_catch_with_lexical_redeclaration() {
check_invalid("try {} catch(e) { let e = 'oh' }");
}
#[test]
fn check_invalid_catch_with_var_redeclaration() {
check_invalid("try {} catch(e) { var e = 'oh' }");
}

69
boa_engine/src/syntax/parser/tests.rs

@ -1,28 +1,32 @@
//! Tests for the parser.
use super::Parser;
use crate::syntax::ast::{
node::{
field::GetConstField, object::PropertyDefinition, ArrowFunctionDecl, Assign, BinOp, Call,
Declaration, DeclarationList, FormalParameter, FormalParameterList,
FormalParameterListFlags, FunctionDecl, Identifier, If, New, Node, Object, Return,
StatementList, UnaryOp,
use crate::{
syntax::ast::{
node::{
field::GetConstField, object::PropertyDefinition, ArrowFunctionDecl, Assign, BinOp,
Call, Declaration, DeclarationList, FormalParameter, FormalParameterList,
FormalParameterListFlags, FunctionDecl, Identifier, If, New, Node, Object, Return,
StatementList, UnaryOp,
},
op::{self, CompOp, LogOp, NumOp},
Const,
},
op::{self, CompOp, LogOp, NumOp},
Const,
Context,
};
use boa_interner::Interner;
/// Checks that the given JavaScript string gives the expected expression.
#[allow(clippy::unwrap_used)]
#[track_caller]
pub(super) fn check_parser<L>(js: &str, expr: L, interner: &mut Interner)
pub(super) fn check_parser<L>(js: &str, expr: L, interner: Interner)
where
L: Into<Box<[Node]>>,
{
let mut context = Context::new(interner);
assert_eq!(
Parser::new(js.as_bytes(), false)
.parse_all(interner)
.parse_all(&mut context)
.expect("failed to parse"),
StatementList::from(expr)
);
@ -31,9 +35,9 @@ where
/// Checks that the given javascript string creates a parse error.
#[track_caller]
pub(super) fn check_invalid(js: &str) {
let mut interner = Interner::default();
let mut context = Context::default();
assert!(Parser::new(js.as_bytes(), false)
.parse_all(&mut interner)
.parse_all(&mut context)
.is_err());
}
@ -53,7 +57,7 @@ fn check_construct_call_precedence() {
),
vec![],
))],
&mut interner,
interner,
);
}
@ -71,7 +75,7 @@ fn assign_operator_precedence() {
),
)
.into()],
&mut interner,
interner,
);
}
@ -103,7 +107,7 @@ fn hoisting() {
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
],
&mut interner,
interner,
);
let mut interner = Interner::default();
@ -119,7 +123,7 @@ fn hoisting() {
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
DeclarationList::Var(vec![Declaration::new_with_identifier(a, None)].into()).into(),
],
&mut interner,
interner,
);
}
@ -144,7 +148,7 @@ fn ambigous_regex_divide_expression() {
),
)
.into()],
&mut interner,
interner,
);
}
@ -170,7 +174,7 @@ fn two_divisions_in_expression() {
),
)
.into()],
&mut interner,
interner,
);
}
@ -202,7 +206,7 @@ fn comment_semi_colon_insertion() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -236,7 +240,7 @@ fn multiline_comment_semi_colon_insertion() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -267,7 +271,7 @@ fn multiline_comment_no_lineterminator() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -298,7 +302,7 @@ fn assignment_line_terminator() {
)
.into(),
],
&mut interner,
interner,
);
}
@ -329,7 +333,7 @@ fn assignment_multiline_terminator() {
.into(),
Assign::new(Identifier::new(a), Const::from(5)).into(),
],
&mut interner,
interner,
);
}
@ -341,7 +345,7 @@ fn bracketed_expr() {
check_parser(
s,
vec![Identifier::new(interner.get_or_intern_static("b")).into()],
&mut interner,
interner,
);
}
@ -359,7 +363,7 @@ fn increment_in_comma_op() {
Identifier::new(b).into(),
)
.into()],
&mut interner,
interner,
);
}
@ -389,7 +393,7 @@ fn spread_in_object() {
.into(),
)
.into()],
&mut interner,
interner,
);
}
@ -419,7 +423,7 @@ fn spread_in_arrow_function() {
vec![Identifier::from(b).into()],
)
.into()],
&mut interner,
interner,
);
}
@ -448,37 +452,34 @@ fn empty_statement() {
None,
)),
],
&mut interner,
interner,
);
}
#[test]
fn hashbang_use_strict_no_with() {
let mut interner = Interner::default();
check_parser(
r#"#!\"use strict"
"#,
vec![],
&mut interner,
Interner::default(),
);
}
#[test]
#[ignore]
fn hashbang_use_strict_with_with_statement() {
let mut interner = Interner::default();
check_parser(
r#"#!\"use strict"
with({}) {}
"#,
vec![],
&mut interner,
Interner::default(),
);
}
#[test]
fn hashbang_comment() {
let mut interner = Interner::default();
check_parser(r"#!Comment Here", vec![], &mut interner);
check_parser(r"#!Comment Here", vec![], Interner::default());
}

28
boa_tester/src/exec/mod.rs

@ -7,7 +7,6 @@ use super::{
TestSuite, IGNORED,
};
use boa_engine::{syntax::Parser, Context, JsResult, JsValue};
use boa_interner::Interner;
use colored::Colorize;
use rayon::prelude::*;
use std::panic;
@ -161,9 +160,10 @@ impl Test {
let res = panic::catch_unwind(|| match self.expected_outcome {
Outcome::Positive => {
// TODO: implement async and add `harness/doneprintHandle.js` to the includes.
let mut context = Context::default();
match self.set_up_env(harness, strict) {
Ok(mut context) => {
match self.set_up_env(harness, strict, &mut context) {
Ok(_) => {
context.set_strict_mode(strict);
let res = context.eval(&self.content.as_ref());
@ -207,14 +207,14 @@ impl Test {
phase: Phase::Runtime,
ref error_type,
} => {
let mut interner = Interner::default();
let mut context = Context::default();
if let Err(e) =
Parser::new(self.content.as_bytes(), strict).parse_all(&mut interner)
Parser::new(self.content.as_bytes(), strict).parse_all(&mut context)
{
(false, format!("Uncaught {e}"))
} else {
match self.set_up_env(harness, strict) {
Ok(mut context) => {
match self.set_up_env(harness, strict, &mut context) {
Ok(_) => {
context.set_strict_mode(strict);
match context.eval(&self.content.as_ref()) {
Ok(res) => (false, res.display().to_string()),
@ -307,15 +307,17 @@ impl Test {
}
/// Sets the environment up to run the test.
fn set_up_env(&self, harness: &Harness, strict: bool) -> Result<Context, String> {
// Create new Realm
let mut context = Context::default();
fn set_up_env(
&self,
harness: &Harness,
strict: bool,
context: &mut Context,
) -> Result<(), String> {
// Register the print() function.
context.register_global_function("print", 1, test262_print);
// add the $262 object.
let _js262 = js262::init(&mut context);
let _js262 = js262::init(context);
if strict {
context
@ -347,7 +349,7 @@ impl Test {
})?;
}
Ok(context)
Ok(())
}
}

Loading…
Cancel
Save