diff --git a/core/ast/src/expression/literal/object.rs b/core/ast/src/expression/literal/object.rs index 7bea065f919..d43effb4230 100644 --- a/core/ast/src/expression/literal/object.rs +++ b/core/ast/src/expression/literal/object.rs @@ -13,6 +13,7 @@ use crate::{ property::{MethodDefinitionKind, PropertyName}, scope::FunctionScopes, visitor::{VisitWith, Visitor, VisitorMut}, + LinearPosition, LinearSpan, LinearSpanIgnoreEq, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; @@ -410,6 +411,7 @@ pub struct ObjectMethodDefinition { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl ObjectMethodDefinition { @@ -421,9 +423,12 @@ impl ObjectMethodDefinition { parameters: FormalParameterList, body: FunctionBody, kind: MethodDefinitionKind, + start_linear_pos: LinearPosition, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); + let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()).into(); + Self { name, parameters, @@ -431,6 +436,7 @@ impl ObjectMethodDefinition { contains_direct_eval, kind, scopes: FunctionScopes::default(), + linear_span, } } @@ -469,6 +475,13 @@ impl ObjectMethodDefinition { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the object method definition contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/arrow_function.rs b/core/ast/src/function/arrow_function.rs index 97c4e259294..f1e721fe10b 100644 --- a/core/ast/src/function/arrow_function.rs +++ b/core/ast/src/function/arrow_function.rs @@ -5,6 +5,7 @@ use crate::{ expression::{Expression, Identifier}, join_nodes, }; +use crate::{LinearSpan, LinearSpanIgnoreEq}; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -30,6 +31,7 @@ pub struct ArrowFunction { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl ArrowFunction { @@ -40,6 +42,7 @@ impl ArrowFunction { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); @@ -49,6 +52,7 @@ impl ArrowFunction { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -86,6 +90,13 @@ impl ArrowFunction { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the arrow function contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/async_arrow_function.rs b/core/ast/src/function/async_arrow_function.rs index ede35b562c7..dfb517c6ca5 100644 --- a/core/ast/src/function/async_arrow_function.rs +++ b/core/ast/src/function/async_arrow_function.rs @@ -8,6 +8,7 @@ use crate::{ expression::{Expression, Identifier}, join_nodes, }; +use crate::{LinearSpan, LinearSpanIgnoreEq}; use boa_interner::{Interner, ToIndentedString}; /// An async arrow function expression, as defined by the [spec]. @@ -30,6 +31,7 @@ pub struct AsyncArrowFunction { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl AsyncArrowFunction { @@ -40,6 +42,7 @@ impl AsyncArrowFunction { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: LinearSpan, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); @@ -49,6 +52,7 @@ impl AsyncArrowFunction { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -86,6 +90,13 @@ impl AsyncArrowFunction { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the function declaration contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/async_function.rs b/core/ast/src/function/async_function.rs index 834e3c755aa..b90c8a4bb3f 100644 --- a/core/ast/src/function/async_function.rs +++ b/core/ast/src/function/async_function.rs @@ -8,7 +8,7 @@ use crate::{ operations::{contains, ContainsSymbol}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, - Declaration, + Declaration, LinearSpan, LinearSpanIgnoreEq, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -32,13 +32,19 @@ pub struct AsyncFunctionDeclaration { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl AsyncFunctionDeclaration { /// Creates a new async function declaration. #[inline] #[must_use] - pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + pub fn new( + name: Identifier, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: LinearSpan, + ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { @@ -47,6 +53,7 @@ impl AsyncFunctionDeclaration { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -78,6 +85,13 @@ impl AsyncFunctionDeclaration { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the async function declaration contains a direct call to `eval`. #[inline] #[must_use] @@ -147,6 +161,7 @@ pub struct AsyncFunctionExpression { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl AsyncFunctionExpression { @@ -157,6 +172,7 @@ impl AsyncFunctionExpression { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: LinearSpan, has_binding_identifier: bool, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) @@ -169,6 +185,7 @@ impl AsyncFunctionExpression { name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -214,6 +231,13 @@ impl AsyncFunctionExpression { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the async function expression contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/async_generator.rs b/core/ast/src/function/async_generator.rs index 663a9f5c30b..6e2f9de98b9 100644 --- a/core/ast/src/function/async_generator.rs +++ b/core/ast/src/function/async_generator.rs @@ -7,6 +7,7 @@ use crate::{ expression::{Expression, Identifier}, join_nodes, Declaration, }; +use crate::{LinearSpan, LinearSpanIgnoreEq}; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -31,13 +32,19 @@ pub struct AsyncGeneratorDeclaration { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl AsyncGeneratorDeclaration { /// Creates a new async generator declaration. #[inline] #[must_use] - pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + pub fn new( + name: Identifier, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: LinearSpan, + ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { @@ -46,6 +53,7 @@ impl AsyncGeneratorDeclaration { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -77,6 +85,13 @@ impl AsyncGeneratorDeclaration { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the async generator declaration contains a direct call to `eval`. #[inline] #[must_use] @@ -146,6 +161,7 @@ pub struct AsyncGeneratorExpression { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl AsyncGeneratorExpression { @@ -156,6 +172,7 @@ impl AsyncGeneratorExpression { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: LinearSpan, has_binding_identifier: bool, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) @@ -168,6 +185,7 @@ impl AsyncGeneratorExpression { name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -213,6 +231,13 @@ impl AsyncGeneratorExpression { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the async generator expression contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/class.rs b/core/ast/src/function/class.rs index 587f0668b7f..0e039b35c78 100644 --- a/core/ast/src/function/class.rs +++ b/core/ast/src/function/class.rs @@ -7,7 +7,7 @@ use crate::{ property::{MethodDefinitionKind, PropertyName}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, - Declaration, + Declaration, LinearPosition, LinearSpan, LinearSpanIgnoreEq, }; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; @@ -690,6 +690,7 @@ pub struct ClassMethodDefinition { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl ClassMethodDefinition { @@ -702,9 +703,13 @@ impl ClassMethodDefinition { body: FunctionBody, kind: MethodDefinitionKind, is_static: bool, + start_linear_pos: LinearPosition, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); + + let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()); + Self { name, parameters, @@ -713,6 +718,7 @@ impl ClassMethodDefinition { kind, is_static, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -765,6 +771,13 @@ impl ClassMethodDefinition { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the class method definition contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/generator.rs b/core/ast/src/function/generator.rs index c1e08f42557..1bfd0d970ca 100644 --- a/core/ast/src/function/generator.rs +++ b/core/ast/src/function/generator.rs @@ -6,7 +6,7 @@ use crate::{ operations::{contains, ContainsSymbol}, scope::{FunctionScopes, Scope}, visitor::{VisitWith, Visitor, VisitorMut}, - Declaration, + Declaration, LinearSpan, LinearSpanIgnoreEq, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -30,13 +30,19 @@ pub struct GeneratorDeclaration { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl GeneratorDeclaration { /// Creates a new generator declaration. #[inline] #[must_use] - pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + pub fn new( + name: Identifier, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: LinearSpan, + ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { @@ -45,6 +51,7 @@ impl GeneratorDeclaration { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -76,6 +83,13 @@ impl GeneratorDeclaration { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the generator declaration contains a direct call to `eval`. #[inline] #[must_use] @@ -145,6 +159,7 @@ pub struct GeneratorExpression { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl GeneratorExpression { @@ -155,6 +170,7 @@ impl GeneratorExpression { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: LinearSpan, has_binding_identifier: bool, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) @@ -167,6 +183,7 @@ impl GeneratorExpression { name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -212,6 +229,13 @@ impl GeneratorExpression { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the generator expression contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/function/mod.rs b/core/ast/src/function/mod.rs index d463de9f50d..d30dcbbbbef 100644 --- a/core/ast/src/function/mod.rs +++ b/core/ast/src/function/mod.rs @@ -49,7 +49,7 @@ pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFl use crate::{ visitor::{VisitWith, Visitor, VisitorMut}, - StatementList, StatementListItem, + LinearPosition, StatementList, StatementListItem, }; /// A Function body. @@ -71,12 +71,12 @@ pub struct FunctionBody { impl FunctionBody { /// Creates a new `FunctionBody` AST node. #[must_use] - pub fn new(statements: S, strict: bool) -> Self + pub fn new(statements: S, linear_pos_end: LinearPosition, strict: bool) -> Self where S: Into>, { Self { - statements: StatementList::new(statements.into(), strict), + statements: StatementList::new(statements.into(), linear_pos_end, strict), } } @@ -100,6 +100,13 @@ impl FunctionBody { pub const fn strict(&self) -> bool { self.statements.strict() } + + /// Get end of linear position in source code. + #[inline] + #[must_use] + pub const fn linear_pos_end(&self) -> LinearPosition { + self.statements.linear_pos_end() + } } impl From for FunctionBody { diff --git a/core/ast/src/function/ordinary_function.rs b/core/ast/src/function/ordinary_function.rs index f0c05876fcf..77eb3e66efc 100644 --- a/core/ast/src/function/ordinary_function.rs +++ b/core/ast/src/function/ordinary_function.rs @@ -7,7 +7,7 @@ use crate::{ scope::{FunctionScopes, Scope}, scope_analyzer::{analyze_binding_escapes, collect_bindings}, visitor::{VisitWith, Visitor, VisitorMut}, - Declaration, + Declaration, LinearSpan, LinearSpanIgnoreEq, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -31,13 +31,19 @@ pub struct FunctionDeclaration { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: LinearSpanIgnoreEq, } impl FunctionDeclaration { /// Creates a new function declaration. #[inline] #[must_use] - pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + pub fn new( + name: Identifier, + parameters: FormalParameterList, + body: FunctionBody, + linear_span: LinearSpan, + ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) || contains(&body, ContainsSymbol::DirectEval); Self { @@ -46,6 +52,7 @@ impl FunctionDeclaration { body, contains_direct_eval, scopes: FunctionScopes::default(), + linear_span: linear_span.into(), } } @@ -77,6 +84,13 @@ impl FunctionDeclaration { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span.0 + } + /// Returns `true` if the function declaration contains a direct call to `eval`. #[inline] #[must_use] @@ -133,7 +147,7 @@ impl From for Declaration { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct FunctionExpression { pub(crate) name: Option, pub(crate) parameters: FormalParameterList, @@ -146,6 +160,20 @@ pub struct FunctionExpression { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) scopes: FunctionScopes, + linear_span: Option, +} + +impl PartialEq for FunctionExpression { + fn eq(&self, other: &Self) -> bool { + // all fields except for `linear_span` + self.name == other.name + && self.parameters == other.parameters + && self.body == other.body + && self.has_binding_identifier == other.has_binding_identifier + && self.contains_direct_eval == other.contains_direct_eval + && self.name_scope == other.name_scope + && self.scopes == other.scopes + } } impl FunctionExpression { @@ -156,6 +184,7 @@ impl FunctionExpression { name: Option, parameters: FormalParameterList, body: FunctionBody, + linear_span: Option, has_binding_identifier: bool, ) -> Self { let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) @@ -168,6 +197,8 @@ impl FunctionExpression { name_scope: None, contains_direct_eval, scopes: FunctionScopes::default(), + #[allow(clippy::redundant_closure_for_method_calls)] + linear_span, } } @@ -213,6 +244,13 @@ impl FunctionExpression { &self.scopes } + /// Gets linear span of the function declaration. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> Option { + self.linear_span + } + /// Returns `true` if the function expression contains a direct call to `eval`. #[inline] #[must_use] diff --git a/core/ast/src/lib.rs b/core/ast/src/lib.rs index edaafdc5706..0ed7a1d818d 100644 --- a/core/ast/src/lib.rs +++ b/core/ast/src/lib.rs @@ -28,6 +28,7 @@ mod module_item_list; mod position; mod punctuator; mod source; +mod source_text; mod statement_list; pub mod declaration; @@ -51,9 +52,10 @@ pub use self::{ expression::Expression, keyword::Keyword, module_item_list::{ModuleItem, ModuleItemList}, - position::{Position, Span}, + position::{LinearPosition, LinearSpan, LinearSpanIgnoreEq, Position, PositionGroup, Span}, punctuator::Punctuator, source::{Module, Script}, + source_text::SourceText, statement::Statement, statement_list::{StatementList, StatementListItem}, }; diff --git a/core/ast/src/operations.rs b/core/ast/src/operations.rs index ca5802e598a..ec8c55c9b91 100644 --- a/core/ast/src/operations.rs +++ b/core/ast/src/operations.rs @@ -29,7 +29,8 @@ use crate::{ LabelledItem, With, }, visitor::{NodeRef, VisitWith, Visitor}, - Declaration, Expression, ModuleItem, Script, Statement, StatementList, StatementListItem, + Declaration, Expression, LinearSpan, ModuleItem, Script, Statement, StatementList, + StatementListItem, }; /// Represents all the possible symbols searched for by the [`Contains`][contains] operation. @@ -2007,6 +2008,18 @@ impl VarScopedDeclaration { Self::AsyncGeneratorDeclaration(g) => bound_names(g), } } + + /// Return [`LinearSpan`] of this declaration (if there is). + #[must_use] + pub fn linear_span(&self) -> Option { + match self { + VarScopedDeclaration::FunctionDeclaration(f) => Some(f.linear_span()), + VarScopedDeclaration::GeneratorDeclaration(f) => Some(f.linear_span()), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => Some(f.linear_span()), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => Some(f.linear_span()), + VarScopedDeclaration::VariableDeclaration(_) => None, + } + } } /// Returns a list of var scoped declarations of the given node. diff --git a/core/ast/src/position.rs b/core/ast/src/position.rs index a42a1768103..fb3d1f366f0 100644 --- a/core/ast/src/position.rs +++ b/core/ast/src/position.rs @@ -52,6 +52,42 @@ impl fmt::Display for Position { } } +impl From for Position { + fn from(value: PositionGroup) -> Self { + value.pos + } +} + +/// Linear position in the ECMAScript source code. +/// +/// Stores linear position in the source code. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct LinearPosition { + pos: usize, +} + +impl LinearPosition { + /// Creates a new `LinearPosition`. + #[inline] + #[must_use] + pub const fn new(pos: usize) -> Self { + Self { pos } + } + /// Gets the linear position. + #[inline] + #[must_use] + pub const fn pos(self) -> usize { + self.pos + } +} +impl fmt::Display for LinearPosition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.pos()) + } +} + /// A span in the ECMAScript source code. /// /// Stores a start position and an end position. @@ -133,11 +169,176 @@ impl fmt::Display for Span { } } +/// A linear span in the ECMAScript source code. +/// +/// Stores a linear start position and a linear end position. +/// +/// Note that linear spans are of the form [start, end) i.e. that the +/// start position is inclusive and the end position is exclusive. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LinearSpan { + start: LinearPosition, + end: LinearPosition, +} +impl LinearSpan { + /// Creates a new `LinearPosition`. + /// + /// # Panics + /// + /// Panics if the start position is bigger than the end position. + #[inline] + #[track_caller] + #[must_use] + pub const fn new(start: LinearPosition, end: LinearPosition) -> Self { + assert!( + start.pos <= end.pos, + "a linear span cannot start after its end" + ); + + Self { start, end } + } + + /// Test if the span is empty. + #[inline] + #[must_use] + pub fn is_empty(self) -> bool { + self.start == self.end + } + + /// Gets the starting position of the span. + #[inline] + #[must_use] + pub const fn start(self) -> LinearPosition { + self.start + } + + /// Gets the final position of the span. + #[inline] + #[must_use] + pub const fn end(self) -> LinearPosition { + self.end + } + + /// Checks if this span inclusively contains another span or position. + pub fn contains(self, other: S) -> bool + where + S: Into, + { + let other = other.into(); + self.start <= other.start && self.end >= other.end + } + + /// Gets the starting position of the span. + #[inline] + #[must_use] + pub fn union(self, other: impl Into) -> Self { + let other: Self = other.into(); + Self { + start: LinearPosition::new(self.start.pos.min(other.start.pos)), + end: LinearPosition::new(self.end.pos.max(other.end.pos)), + } + } +} +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for LinearSpan { + fn arbitrary(_: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let zero_pos = LinearPosition::new(0); + Ok(Self::new(zero_pos, zero_pos)) + } +} + +impl From for LinearSpan { + fn from(pos: LinearPosition) -> Self { + Self { + start: pos, + end: pos, + } + } +} + +impl PartialOrd for LinearSpan { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + Some(Ordering::Equal) + } else if self.end < other.start { + Some(Ordering::Less) + } else if self.start > other.end { + Some(Ordering::Greater) + } else { + None + } + } +} + +/// Stores a `LinearSpan` but `PartialEq`, `Eq` always return true. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy)] +pub struct LinearSpanIgnoreEq(pub LinearSpan); +impl PartialEq for LinearSpanIgnoreEq { + fn eq(&self, _: &Self) -> bool { + true + } +} +impl From for LinearSpanIgnoreEq { + fn from(value: LinearSpan) -> Self { + Self(value) + } +} +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for LinearSpanIgnoreEq { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self(LinearSpan::arbitrary(u)?)) + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// A position group of `LinearPosition` and `Position` related to the same position in the ECMAScript source code. +pub struct PositionGroup { + pos: Position, + linear_pos: LinearPosition, +} +impl PositionGroup { + /// Creates a new `PositionGroup`. + #[inline] + #[must_use] + pub const fn new(pos: Position, linear_pos: LinearPosition) -> Self { + Self { pos, linear_pos } + } + /// Get the `Position`. + #[inline] + #[must_use] + pub fn position(&self) -> Position { + self.pos + } + /// Get the `LinearPosition`. + #[inline] + #[must_use] + pub fn linear_position(&self) -> LinearPosition { + self.linear_pos + } + + /// Gets the line number of the position. + #[inline] + #[must_use] + pub const fn line_number(&self) -> u32 { + self.pos.line_number() + } + + /// Gets the column number of the position. + #[inline] + #[must_use] + pub const fn column_number(&self) -> u32 { + self.pos.column_number() + } +} + #[cfg(test)] mod tests { #![allow(clippy::similar_names)] #![allow(unused_must_use)] - use super::{Position, Span}; + use super::{LinearPosition, LinearSpan, Position, Span}; /// Checks that we cannot create a position with 0 as the column. #[test] @@ -162,6 +363,13 @@ mod tests { assert_ne!(Position::new(10, 50), Position::new(11, 51)); } + /// Checks that the `PartialEq` implementation of `LinearPosition` is consistent. + #[test] + fn linear_position_equality() { + assert_eq!(LinearPosition::new(1050), LinearPosition::new(1050)); + assert_ne!(LinearPosition::new(1050), LinearPosition::new(1051)); + } + /// Checks that the `PartialOrd` implementation of `Position` is consistent. #[test] fn position_order() { @@ -176,6 +384,13 @@ mod tests { assert!(Position::new(11, 49) > Position::new(10, 50)); } + /// Checks that the `PartialOrd` implementation of `LinearPosition` is consistent. + #[test] + fn linear_position_order() { + assert!(LinearPosition::new(1050) < LinearPosition::new(1051)); + assert!(LinearPosition::new(1149) > LinearPosition::new(1050)); + } + /// Checks that the position getters actually retrieve correct values. #[test] fn position_getters() { @@ -202,6 +417,15 @@ mod tests { Span::new(b, a); } + /// Checks that we cannot create an invalid linear span. + #[test] + #[should_panic(expected = "a linear span cannot start after its end")] + fn invalid_linear_span() { + let a = LinearPosition::new(1030); + let b = LinearPosition::new(1050); + LinearSpan::new(b, a); + } + /// Checks that we can create valid spans. #[test] fn span_creation() { @@ -213,6 +437,17 @@ mod tests { Span::from(a); } + /// Checks that we can create valid linear spans. + #[test] + fn linear_span_creation() { + let a = LinearPosition::new(1030); + let b = LinearPosition::new(1050); + + LinearSpan::new(a, b); + let span_aa = LinearSpan::new(a, a); + assert_eq!(LinearSpan::from(a), span_aa); + } + /// Checks that the `PartialEq` implementation of `Span` is consistent. #[test] fn span_equality() { @@ -236,6 +471,24 @@ mod tests { assert_eq!(span_a, span_aa); } + /// Checks that the `PartialEq` implementation of `LinearSpan` is consistent. + #[test] + fn linear_span_equality() { + let a = LinearPosition::new(1030); + let b = LinearPosition::new(1050); + let c = LinearPosition::new(1150); + + let span_ab = LinearSpan::new(a, b); + let span_ab_2 = LinearSpan::new(a, b); + let span_ac = LinearSpan::new(a, c); + let span_bc = LinearSpan::new(b, c); + + assert_eq!(span_ab, span_ab_2); + assert_ne!(span_ab, span_ac); + assert_ne!(span_ab, span_bc); + assert_ne!(span_bc, span_ac); + } + /// Checks that the getters retrieve the correct value. #[test] fn span_getters() { @@ -278,6 +531,36 @@ mod tests { assert!(!span_bd.contains(span_ac)); } + /// Checks that the `LinearSpan::contains()` method works properly. + #[test] + fn linear_span_contains() { + let a = LinearPosition::new(1050); + let b = LinearPosition::new(1080); + let c = LinearPosition::new(1120); + let d = LinearPosition::new(1125); + + let span_ac = LinearSpan::new(a, c); + assert!(span_ac.contains(b)); + + let span_ab = LinearSpan::new(a, b); + let span_cd = LinearSpan::new(c, d); + + assert!(!span_ab.contains(span_cd)); + assert!(span_ab.contains(b)); + + let span_ad = LinearSpan::new(a, d); + let span_bc = LinearSpan::new(b, c); + + assert!(span_ad.contains(span_bc)); + assert!(!span_bc.contains(span_ad)); + + let span_ac = LinearSpan::new(a, c); + let span_bd = LinearSpan::new(b, d); + + assert!(!span_ac.contains(span_bd)); + assert!(!span_bd.contains(span_ac)); + } + /// Checks that the string representation of a span is correct. #[test] fn span_to_string() { @@ -303,4 +586,57 @@ mod tests { assert!(span_ab < span_cd); assert!(span_cd > span_ab); } + + /// Checks that the ordering of linear spans is correct. + #[test] + fn linear_span_ordering() { + let a = LinearPosition::new(1050); + let b = LinearPosition::new(1052); + let c = LinearPosition::new(1120); + let d = LinearPosition::new(1125); + + let span_ab = LinearSpan::new(a, b); + let span_cd = LinearSpan::new(c, d); + + let span_ac = LinearSpan::new(a, c); + let span_bd = LinearSpan::new(b, d); + + assert!(span_ab < span_cd); + assert!(span_cd > span_ab); + assert_eq!(span_bd.partial_cmp(&span_ac), None); + assert_eq!(span_ac.partial_cmp(&span_bd), None); + } + + /// Checks that the ordering of linear spans is correct. + #[test] + fn linear_union() { + let a = LinearPosition::new(1050); + let b = LinearPosition::new(1052); + let c = LinearPosition::new(1120); + let d = LinearPosition::new(1125); + + let span_ab = LinearSpan::new(a, b); + let span_ad = LinearSpan::new(a, d); + let span_bc = LinearSpan::new(b, c); + let span_cd = LinearSpan::new(c, d); + let span_ac = LinearSpan::new(a, c); + let span_bd = LinearSpan::new(b, d); + + assert_eq!(span_bd.union(a), span_ad); + assert_eq!(span_ab.union(a), span_ab); + assert_eq!(span_bd.union(span_ac), span_ad); + assert_eq!(span_ac.union(span_bd), span_ad); + assert_eq!(span_ac.union(span_bd), span_ad); + assert_eq!(span_ac.union(b), span_ac); + assert_eq!(span_bc.union(span_ab), span_ac); + assert_eq!(span_ab.union(span_bc), span_ac); + assert_eq!(span_ac.union(span_ab), span_ac); + assert_eq!(span_cd.union(a), span_ad); + assert_eq!(span_cd.union(span_bc), span_bd); + } } + +// TODO: union Span & LinearSpan into `SpanBase` and then: +// * Span = SpanBase; +// * LinearSpan = SpanBase; +// ? diff --git a/core/ast/src/source.rs b/core/ast/src/source.rs index e7a4ad1633b..0dddb0e8147 100644 --- a/core/ast/src/source.rs +++ b/core/ast/src/source.rs @@ -20,8 +20,7 @@ use crate::{ /// /// [spec]: https://tc39.es/ecma262/#sec-scripts #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default)] pub struct Script { statements: StatementList, } @@ -123,6 +122,20 @@ impl ToIndentedString for Script { } } +impl PartialEq for Script { + fn eq(&self, other: &Self) -> bool { + self.statements == other.statements + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Script { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let statements = StatementList::arbitrary(u)?; + Ok(Self { statements }) + } +} + /// A Module source. /// /// More information: diff --git a/core/ast/src/source_text.rs b/core/ast/src/source_text.rs new file mode 100644 index 00000000000..bbd564d3f5b --- /dev/null +++ b/core/ast/src/source_text.rs @@ -0,0 +1,77 @@ +use crate::{LinearPosition, LinearSpan}; + +/// Source text. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct SourceText { + source_text: Vec, +} + +impl SourceText { + /// Constructs a new, empty `SourceText` with at least the specified capacity. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + source_text: Vec::with_capacity(capacity), + } + } + + /// Get current `LinearPosition`. + #[must_use] + pub fn cur_linear_position(&self) -> LinearPosition { + LinearPosition::new(self.source_text.len()) + } + + /// Get code points from `pos` to the current end. + #[must_use] + pub fn get_code_points_from_pos(&self, pos: LinearPosition) -> &[u16] { + &self.source_text[pos.pos()..] + } + + /// Get code points within `span`. + #[must_use] + pub fn get_code_points_from_span(&self, span: LinearSpan) -> &[u16] { + &self.source_text[span.start().pos()..span.end().pos()] + } + + /// Remove last code point. + #[inline] + pub fn remove_last_code_point(&mut self) { + self.source_text.pop(); + } + + /// Collect code point. + /// + /// # Panics + /// + /// On invalid code point. + #[inline] + pub fn collect_code_point(&mut self, cp: u32) { + if let Ok(cu) = cp.try_into() { + self.push(cu); + return; + } + let cp = cp - 0x10000; + let cu1 = (cp / 0x400 + 0xD800) + .try_into() + .expect("Invalid code point"); + let cu2 = (cp % 0x400 + 0xDC00) + .try_into() + .expect("Invalid code point"); + self.push(cu1); + self.push(cu2); + } + + #[inline] + fn push(&mut self, cp: u16) { + self.source_text.push(cp); + } +} + +const DEFAULT_CAPACITY: usize = 4 * 1024; + +impl Default for SourceText { + fn default() -> Self { + Self::with_capacity(DEFAULT_CAPACITY) + } +} diff --git a/core/ast/src/statement_list.rs b/core/ast/src/statement_list.rs index 9d08a8bfdd4..4d6d218d779 100644 --- a/core/ast/src/statement_list.rs +++ b/core/ast/src/statement_list.rs @@ -4,6 +4,7 @@ use super::Declaration; use crate::{ statement::Statement, visitor::{VisitWith, Visitor, VisitorMut}, + LinearPosition, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; @@ -95,21 +96,29 @@ impl VisitWith for StatementListItem { /// /// [spec]: https://tc39.es/ecma262/#prod-StatementList #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default)] pub struct StatementList { pub(crate) statements: Box<[StatementListItem]>, + linear_pos_end: LinearPosition, strict: bool, } +impl PartialEq for StatementList { + fn eq(&self, other: &Self) -> bool { + self.statements == other.statements && self.strict == other.strict + } +} + impl StatementList { /// Creates a new `StatementList` AST node. #[must_use] - pub fn new(statements: S, strict: bool) -> Self + pub fn new(statements: S, linear_pos_end: LinearPosition, strict: bool) -> Self where S: Into>, { Self { statements: statements.into(), + linear_pos_end, strict, } } @@ -127,23 +136,32 @@ impl StatementList { pub const fn strict(&self) -> bool { self.strict } + + /// Get end of linear position in source code. + #[inline] + #[must_use] + pub const fn linear_pos_end(&self) -> LinearPosition { + self.linear_pos_end + } } -impl From> for StatementList { +impl From<(Box<[StatementListItem]>, LinearPosition)> for StatementList { #[inline] - fn from(stm: Box<[StatementListItem]>) -> Self { + fn from(value: (Box<[StatementListItem]>, LinearPosition)) -> Self { Self { - statements: stm, + statements: value.0, + linear_pos_end: value.1, strict: false, } } } -impl From> for StatementList { +impl From<(Vec, LinearPosition)> for StatementList { #[inline] - fn from(stm: Vec) -> Self { + fn from(value: (Vec, LinearPosition)) -> Self { Self { - statements: stm.into(), + statements: value.0.into(), + linear_pos_end: value.1, strict: false, } } @@ -198,6 +216,7 @@ impl<'a> arbitrary::Arbitrary<'a> for StatementList { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { Ok(Self { statements: u.arbitrary()?, + linear_pos_end: LinearPosition::default(), strict: false, // disable strictness; this is *not* in source data }) } diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index c86b084fee4..67023a0d2c9 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -18,9 +18,10 @@ use crate::{ js_string, object::JsObject, realm::Realm, + spanned_source_text::SourceText, string::StaticJsStrings, vm::{CallFrame, CallFrameFlags, Constant, Opcode}, - Context, JsArgs, JsResult, JsString, JsValue, + Context, JsArgs, JsResult, JsString, JsValue, SpannedSourceText, }; use boa_ast::{ operations::{contains, contains_arguments, ContainsSymbol}, @@ -130,7 +131,7 @@ impl Eval { if strict { parser.set_strict(); } - let mut body = parser.parse_eval(direct, context.interner_mut())?; + let (mut body, source) = parser.parse_eval(direct, context.interner_mut())?; // 6. Let inFunction be false. // 7. Let inMethod be false. @@ -261,6 +262,9 @@ impl Eval { let in_with = context.vm.environments.has_object_environment(); + let source_text = SourceText::new(source); + let spanned_source_text = SpannedSourceText::new_source_only(source_text); + let mut compiler = ByteCompiler::new( js_string!("
"), body.strict(), @@ -271,6 +275,7 @@ impl Eval { false, context.interner_mut(), in_with, + spanned_source_text, ); compiler.current_open_environments_count += 1; diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 39ac251ed8b..589d803f0ce 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -34,7 +34,7 @@ use crate::{ symbol::JsSymbol, value::IntegerOrInfinity, vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock}, - Context, JsArgs, JsResult, JsStr, JsString, JsValue, + Context, JsArgs, JsResult, JsStr, JsString, JsValue, SpannedSourceText, }; use boa_ast::{ function::{FormalParameterList, FunctionBody}, @@ -637,8 +637,10 @@ impl BuiltInFunctionObject { body }; + // TODO: create SourceText : "anonymous(" parameters \n ") {" body_parse "}" + let mut function = - boa_ast::function::FunctionExpression::new(None, parameters, body, false); + boa_ast::function::FunctionExpression::new(None, parameters, body, None, false); if !function.analyze_scope(strict, context.realm().scope(), context.interner()) { return Err(JsNativeError::syntax() .with_message("failed to analyze function scope") @@ -646,7 +648,9 @@ impl BuiltInFunctionObject { } let in_with = context.vm.environments.has_object_environment(); - let code = FunctionCompiler::new() + let spanned_source_text = SpannedSourceText::new_empty(); + + let code = FunctionCompiler::new(spanned_source_text) .name(js_string!("anonymous")) .generator(generator) .r#async(r#async) @@ -869,6 +873,9 @@ impl BuiltInFunctionObject { .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; let code = function.codeblock(); + if let Some(code_points) = code.source_text_spanned.to_code_points() { + return Ok(JsString::from(code_points).into()); + } Ok(js_string!( js_str!("function "), diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 78e9710f554..d7d536748ee 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -32,7 +32,7 @@ use crate::{ symbol::JsSymbol, value::IntegerOrInfinity, vm::{CallFrame, CallFrameFlags}, - Context, JsArgs, JsBigInt, JsResult, JsString, JsValue, + Context, JsArgs, JsBigInt, JsResult, JsString, JsValue, SpannedSourceText, }; use boa_gc::Gc; use boa_parser::{Parser, Source}; @@ -112,9 +112,13 @@ impl Json { // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. let mut parser = Parser::new(Source::from_bytes(&script_string)); parser.set_json_parse(); + // In json we don't need the source: there no way to pass an object that needs a source text + // But if it's incorrect, just call `parser.parse_script_with_source` here let script = parser.parse_script(&Scope::new_global(), context.interner_mut())?; let code_block = { let in_with = context.vm.environments.has_object_environment(); + // If the source is needed then call `parser.parse_script_with_source` and pass `source_text` here. + let spanned_source_text = SpannedSourceText::new_empty(); let mut compiler = ByteCompiler::new( js_string!("
"), script.strict(), @@ -125,6 +129,7 @@ impl Json { false, context.interner_mut(), in_with, + spanned_source_text, ); compiler.compile_statement_list(script.statements(), true, false); Gc::new(compiler.finish()) diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 26e666b57fa..1ddbf4c8d8d 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -86,6 +86,8 @@ impl ByteCompiler<'_> { let outer_scope = self.push_declarative_scope(class.name_scope); + // The new span is not the same as the parent `ByteCompiler` have. + let spanned_source_text = self.spanned_source_text.clone_only_source(); let mut compiler = ByteCompiler::new( class_name.clone(), true, @@ -96,6 +98,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + spanned_source_text, ); compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR; @@ -290,6 +293,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + self.spanned_source_text.clone_only_source(), ); // Function environment @@ -327,6 +331,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + self.spanned_source_text.clone_only_source(), ); field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); @@ -369,6 +374,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + self.spanned_source_text.clone_only_source(), ); field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); @@ -405,6 +411,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + self.spanned_source_text.clone_only_source(), ); field_compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = field_compiler.push_scope(field.scope()); @@ -440,6 +447,7 @@ impl ByteCompiler<'_> { false, self.interner, self.in_with, + self.spanned_source_text.clone_only_source(), ); compiler.code_block_flags |= CodeBlockFlags::HAS_FUNCTION_SCOPE; let _ = compiler.push_scope(block.scopes().function_scope()); diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index 73b9d430722..2f4d32fe5bd 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -1,7 +1,7 @@ use crate::{ bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind}, vm::{BindingOpcode, Opcode}, - Context, JsNativeError, JsResult, + Context, JsNativeError, JsResult, SpannedSourceText, }; use boa_ast::{ declaration::Binding, @@ -520,7 +520,10 @@ impl ByteCompiler<'_> { VarScopedDeclaration::VariableDeclaration(_) => continue, }; - let code = FunctionCompiler::new() + let func_span = function.linear_span(); + let spanned_source_text = SpannedSourceText::new(self.source_text(), func_span); + + let code = FunctionCompiler::new(spanned_source_text) .name(name.sym().to_js_string(self.interner())) .generator(generator) .r#async(r#async) @@ -786,7 +789,10 @@ impl ByteCompiler<'_> { } }; - let code = FunctionCompiler::new() + let func_span = function.linear_span(); + let spanned_source_text = SpannedSourceText::new(self.source_text(), func_span); + + let code = FunctionCompiler::new(spanned_source_text) .name(name.sym().to_js_string(self.interner())) .generator(generator) .r#async(r#async) diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index 5909783b025..dc73998a011 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -3,7 +3,7 @@ use crate::{ bytecompiler::ByteCompiler, js_string, vm::{CodeBlock, CodeBlockFlags, Opcode}, - JsString, + JsString, SpannedSourceText, }; use boa_ast::{ function::{FormalParameterList, FunctionBody}, @@ -24,11 +24,12 @@ pub(crate) struct FunctionCompiler { method: bool, in_with: bool, name_scope: Option, + spanned_source_text: SpannedSourceText, } impl FunctionCompiler { /// Create a new `FunctionCompiler`. - pub(crate) fn new() -> Self { + pub(crate) fn new(spanned_source_text: SpannedSourceText) -> Self { Self { name: js_string!(), generator: false, @@ -38,6 +39,7 @@ impl FunctionCompiler { method: false, in_with: false, name_scope: None, + spanned_source_text, } } @@ -119,7 +121,9 @@ impl FunctionCompiler { self.generator, interner, self.in_with, + self.spanned_source_text, ); + compiler.length = length; compiler.code_block_flags.set( CodeBlockFlags::HAS_PROTOTYPE_PROPERTY, diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index b4ec960cc4c..94fefbe708c 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -21,7 +21,7 @@ use crate::{ BindingOpcode, CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, InlineCache, Opcode, VaryingOperandKind, }, - JsBigInt, JsStr, JsString, + JsBigInt, JsStr, JsString, SourceText, SpannedSourceText, }; use boa_ast::{ declaration::{Binding, LexicalDeclaration, VarDeclaration}, @@ -41,7 +41,7 @@ use boa_ast::{ pattern::Pattern, property::MethodDefinitionKind, scope::{BindingLocator, BindingLocatorError, FunctionScopes, IdentifierReference, Scope}, - Declaration, Expression, Statement, StatementList, StatementListItem, + Declaration, Expression, LinearSpan, Statement, StatementList, StatementListItem, }; use boa_gc::Gc; use boa_interner::{Interner, Sym}; @@ -114,7 +114,7 @@ impl FunctionKind { } /// Describes the complete specification of a function node. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub(crate) struct FunctionSpec<'a> { pub(crate) kind: FunctionKind, pub(crate) name: Option, @@ -122,9 +122,22 @@ pub(crate) struct FunctionSpec<'a> { body: &'a FunctionBody, pub(crate) scopes: &'a FunctionScopes, pub(crate) name_scope: Option<&'a Scope>, + linear_span: Option, pub(crate) contains_direct_eval: bool, } +impl PartialEq for FunctionSpec<'_> { + fn eq(&self, other: &Self) -> bool { + // all fields except `linear_span` + self.kind == other.kind + && self.name == other.name + && self.parameters == other.parameters + && self.body == other.body + && self.scopes == other.scopes + && self.name_scope == other.name_scope + } +} + impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> { fn from(function: &'a FunctionDeclaration) -> Self { FunctionSpec { @@ -134,6 +147,7 @@ impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -148,6 +162,7 @@ impl<'a> From<&'a GeneratorDeclaration> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -162,6 +177,7 @@ impl<'a> From<&'a AsyncFunctionDeclaration> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -176,6 +192,7 @@ impl<'a> From<&'a AsyncGeneratorDeclaration> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -190,6 +207,7 @@ impl<'a> From<&'a FunctionExpression> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: function.name_scope(), + linear_span: function.linear_span(), contains_direct_eval: function.contains_direct_eval(), } } @@ -204,6 +222,7 @@ impl<'a> From<&'a ArrowFunction> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -218,6 +237,7 @@ impl<'a> From<&'a AsyncArrowFunction> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: None, + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -232,6 +252,7 @@ impl<'a> From<&'a AsyncFunctionExpression> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: function.name_scope(), + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -246,6 +267,7 @@ impl<'a> From<&'a GeneratorExpression> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: function.name_scope(), + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -260,6 +282,7 @@ impl<'a> From<&'a AsyncGeneratorExpression> for FunctionSpec<'a> { body: function.body(), scopes: function.scopes(), name_scope: function.name_scope(), + linear_span: Some(function.linear_span()), contains_direct_eval: function.contains_direct_eval(), } } @@ -281,6 +304,7 @@ impl<'a> From<&'a ClassMethodDefinition> for FunctionSpec<'a> { body: method.body(), scopes: method.scopes(), name_scope: None, + linear_span: Some(method.linear_span()), contains_direct_eval: method.contains_direct_eval(), } } @@ -302,6 +326,7 @@ impl<'a> From<&'a ObjectMethodDefinition> for FunctionSpec<'a> { body: method.body(), scopes: method.scopes(), name_scope: None, + linear_span: Some(method.linear_span()), contains_direct_eval: method.contains_direct_eval(), } } @@ -443,6 +468,7 @@ pub struct ByteCompiler<'ctx> { pub(crate) emitted_mapped_arguments_object_opcode: bool, pub(crate) interner: &'ctx mut Interner, + spanned_source_text: SpannedSourceText, #[cfg(feature = "annex-b")] pub(crate) annex_b_function_names: Vec, @@ -473,6 +499,7 @@ impl<'ctx> ByteCompiler<'ctx> { is_generator: bool, interner: &'ctx mut Interner, in_with: bool, + spanned_source_text: SpannedSourceText, ) -> ByteCompiler<'ctx> { let mut code_block_flags = CodeBlockFlags::empty(); code_block_flags.set(CodeBlockFlags::STRICT, strict); @@ -535,6 +562,7 @@ impl<'ctx> ByteCompiler<'ctx> { variable_scope, lexical_scope, interner, + spanned_source_text, #[cfg(feature = "annex-b")] annex_b_function_names: Vec::new(), @@ -543,6 +571,10 @@ impl<'ctx> ByteCompiler<'ctx> { } } + pub(crate) fn source_text(&self) -> SourceText { + self.spanned_source_text.source_text() + } + pub(crate) const fn strict(&self) -> bool { self.code_block_flags.contains(CodeBlockFlags::STRICT) } @@ -1531,12 +1563,14 @@ impl<'ctx> ByteCompiler<'ctx> { function.kind.is_async(), function.kind.is_arrow(), ); + let FunctionSpec { name, parameters, body, scopes, name_scope, + linear_span, .. } = function; @@ -1546,7 +1580,9 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let code = FunctionCompiler::new() + let spanned_source_text = SpannedSourceText::new(self.source_text(), linear_span); + + let code = FunctionCompiler::new(spanned_source_text) .name(name) .generator(generator) .r#async(r#async) @@ -1609,6 +1645,7 @@ impl<'ctx> ByteCompiler<'ctx> { body, scopes, name_scope, + linear_span, .. } = function; @@ -1623,7 +1660,9 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let code = FunctionCompiler::new() + let spanned_source_text = SpannedSourceText::new(self.source_text(), linear_span); + + let code = FunctionCompiler::new(spanned_source_text) .name(name) .generator(generator) .r#async(r#async) @@ -1658,6 +1697,7 @@ impl<'ctx> ByteCompiler<'ctx> { parameters, body, scopes, + linear_span, .. } = function; @@ -1667,7 +1707,9 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let code = FunctionCompiler::new() + let spanned_source_text = SpannedSourceText::new(self.source_text(), linear_span); + + let code = FunctionCompiler::new(spanned_source_text) .name(name) .generator(generator) .r#async(r#async) @@ -1833,6 +1875,7 @@ impl<'ctx> ByteCompiler<'ctx> { handlers: self.handlers, flags: Cell::new(self.code_block_flags), ic: self.ic.into_boxed_slice(), + source_text_spanned: self.spanned_source_text, } } diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 3af1e9df233..d897c0b8cc6 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -108,6 +108,10 @@ mod host_defined; mod small_map; mod sys; +mod spanned_source_text; +use spanned_source_text::SourceText; +pub use spanned_source_text::SpannedSourceText; + #[cfg(test)] mod tests; diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index 571f053570f..ee7d0e00c9e 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -42,6 +42,7 @@ use source::SourceTextModule; pub use synthetic::{SyntheticModule, SyntheticModuleInitializer}; use crate::object::TypedJsFunction; +use crate::spanned_source_text::SourceText; use crate::{ builtins, builtins::promise::{PromiseCapability, PromiseState}, @@ -166,9 +167,11 @@ impl Module { let mut parser = Parser::new(src); parser.set_identifier(context.next_parser_identifier()); - let module = parser.parse_module(realm.scope(), context.interner_mut())?; + let (module, source) = + parser.parse_module_with_source(realm.scope(), context.interner_mut())?; - let src = SourceTextModule::new(module, context.interner()); + let source_text = SourceText::new(source); + let src = SourceTextModule::new(module, context.interner(), source_text); Ok(Self { inner: Gc::new(ModuleRepr { diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index e7f10c7f102..f6883b246bd 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -30,10 +30,12 @@ use crate::{ CompletionRecord, Opcode, }, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, + SpannedSourceText, }; use super::{ BindingName, GraphLoadingState, Module, Referrer, ResolveExportError, ResolvedBinding, + SourceText, }; /// Information for the [**Depth-first search**] algorithm used in the @@ -232,6 +234,7 @@ struct ModuleCode { has_tla: bool, requested_modules: IndexSet>, source: boa_ast::Module, + source_text: SourceText, import_entries: Vec, local_export_entries: Vec, indirect_export_entries: Vec, @@ -244,7 +247,7 @@ impl SourceTextModule { /// Contains part of the abstract operation [`ParseModule`][parse]. /// /// [parse]: https://tc39.es/ecma262/#sec-parsemodule - pub(super) fn new(code: boa_ast::Module, interner: &Interner) -> Self { + pub(super) fn new(code: boa_ast::Module, interner: &Interner, source_text: SourceText) -> Self { // 3. Let requestedModules be the ModuleRequests of body. let requested_modules = code .items() @@ -335,6 +338,7 @@ impl SourceTextModule { import_meta: GcRefCell::default(), code: ModuleCode { source: code, + source_text, requested_modules, has_tla, import_entries, @@ -1424,6 +1428,7 @@ impl SourceTextModule { let global_env = realm.environment().clone(); let env = self.code.source.scope().clone(); + let spanned_source_text = SpannedSourceText::new_source_only(self.code.source_text.clone()); let mut compiler = ByteCompiler::new( js_string!("
"), true, @@ -1434,6 +1439,7 @@ impl SourceTextModule { false, context.interner_mut(), false, + spanned_source_text, ); compiler.async_handler = Some(compiler.push_handler()); diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 909a909c8b8..aabbadf9780 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -9,7 +9,7 @@ use crate::{ js_string, object::JsPromise, vm::{ActiveRunnable, CallFrame, CodeBlock}, - Context, JsNativeError, JsResult, JsString, JsValue, Module, + Context, JsNativeError, JsResult, JsString, JsValue, Module, SpannedSourceText, }; use super::{BindingName, ResolveExportError, ResolvedBinding}; @@ -296,6 +296,8 @@ impl SyntheticModule { false, context.interner_mut(), false, + // A synthetic module does not contain `SourceText` + SpannedSourceText::new_empty(), ); // 4. For each String exportName in module.[[ExportNames]], do diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index 4adf0118d91..70f0f2805c4 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -20,8 +20,9 @@ use crate::{ bytecompiler::{global_declaration_instantiation_context, ByteCompiler}, js_string, realm::Realm, + spanned_source_text::SourceText, vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock}, - Context, HostDefined, JsResult, JsString, JsValue, Module, + Context, HostDefined, JsResult, JsString, JsValue, Module, SpannedSourceText, }; /// ECMAScript's [**Script Record**][spec]. @@ -47,6 +48,7 @@ struct Inner { realm: Realm, #[unsafe_ignore_trace] source: boa_ast::Script, + source_text: SourceText, codeblock: GcRefCell>>, loaded_modules: GcRefCell>, host_defined: HostDefined, @@ -91,15 +93,18 @@ impl Script { parser.set_strict(); } let scope = context.realm().scope().clone(); - let mut code = parser.parse_script(&scope, context.interner_mut())?; + let (mut code, source) = parser.parse_script_with_source(&scope, context.interner_mut())?; if !context.optimizer_options().is_empty() { context.optimize_statement_list(code.statements_mut()); } + let source_text = SourceText::new(source); + Ok(Self { inner: Gc::new(Inner { realm: realm.unwrap_or_else(|| context.realm().clone()), source: code, + source_text, codeblock: GcRefCell::default(), loaded_modules: GcRefCell::default(), host_defined: HostDefined::default(), @@ -129,6 +134,7 @@ impl Script { context, )?; + let spanned_source_text = SpannedSourceText::new_source_only(self.get_source()); let mut compiler = ByteCompiler::new( js_string!("
"), self.inner.source.strict(), @@ -139,6 +145,7 @@ impl Script { false, context.interner_mut(), false, + spanned_source_text, ); #[cfg(feature = "annex-b")] @@ -238,4 +245,8 @@ impl Script { pub(super) fn path(&self) -> Option<&Path> { self.inner.path.as_deref() } + + pub(super) fn get_source(&self) -> SourceText { + self.inner.source_text.clone() + } } diff --git a/core/engine/src/spanned_source_text.rs b/core/engine/src/spanned_source_text.rs new file mode 100644 index 00000000000..419a5ff97da --- /dev/null +++ b/core/engine/src/spanned_source_text.rs @@ -0,0 +1,114 @@ +use boa_ast::LinearSpan; +use boa_gc::{Finalize, Gc, Trace}; + +#[derive(Trace, Finalize)] +struct Inner { + #[unsafe_ignore_trace] + source_text: boa_ast::SourceText, +} +impl Inner { + fn new(source_text: boa_ast::SourceText) -> Self { + Self { source_text } + } +} + +#[derive(Trace, Finalize, Clone)] +pub(crate) struct SourceText { + source_text: Option>, +} + +impl SourceText { + #[must_use] + pub(crate) fn new(source_text: boa_ast::SourceText) -> Self { + Self { + source_text: Some(Gc::new(Inner::new(source_text))), + } + } + + fn new_empty() -> Self { + Self { source_text: None } + } + + #[inline] + fn inner(&self) -> Option<&boa_ast::SourceText> { + self.source_text.as_ref().map(|x| &x.source_text) + } + + fn is_empty(&self) -> bool { + self.source_text.is_none() + } +} + +/// Contains pointer to source code and span of the object. +#[derive(Trace, Finalize, Clone)] +pub struct SpannedSourceText { + source_text: SourceText, + #[unsafe_ignore_trace] + span: Option, +} +impl SpannedSourceText { + pub(crate) fn new(source_text: SourceText, span: Option) -> Self { + Self { source_text, span } + } + + pub(crate) fn new_source_only(source_text: SourceText) -> Self { + Self { + source_text, + span: None, + } + } + + pub(crate) fn new_empty() -> Self { + Self { + source_text: SourceText::new_empty(), + span: None, + } + } + + /// Creates new [`SpannedSourceText`] with the same [`SourceText`] but without its span. + pub(crate) fn clone_only_source(&self) -> Self { + Self { + source_text: self.source_text.clone(), + span: None, + } + } + + /// Returns the [`SourceText`]. + #[must_use] + pub(crate) fn source_text(&self) -> SourceText { + self.source_text.clone() + } + + /// Test if the span is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + let span_is_empty = if let Some(x) = self.span { + x.is_empty() + } else { + true + }; + span_is_empty || self.source_text.is_empty() + } + + /// Gets inner code points. + #[must_use] + pub fn to_code_points(&self) -> Option<&[u16]> { + if let (Some(source_text), Some(span)) = (self.source_text.inner(), self.span) { + Some(source_text.get_code_points_from_span(span)) + } else { + None + } + } +} + +impl std::fmt::Debug for SpannedSourceText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SourceTextSpanned").finish() + } +} +impl std::fmt::Debug for SourceText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SourceTextInner").finish() + } +} diff --git a/core/engine/src/tests/mod.rs b/core/engine/src/tests/mod.rs index ca9d2859462..b01ab5e3506 100644 --- a/core/engine/src/tests/mod.rs +++ b/core/engine/src/tests/mod.rs @@ -12,6 +12,7 @@ mod iterators; mod operators; mod promise; mod spread; +mod to_string; use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; diff --git a/core/engine/src/tests/to_string.rs b/core/engine/src/tests/to_string.rs new file mode 100644 index 00000000000..98bda972435 --- /dev/null +++ b/core/engine/src/tests/to_string.rs @@ -0,0 +1,460 @@ +#![allow(clippy::items_after_statements)] + +use std::fmt::Debug; + +use crate::{value::TryFromJs, Context}; +use boa_parser::Source; + +/// We test nested `toString()` cases: +/// * an ordinary within an ordinary // `ordinary_1` --> `ordinary_2` +/// * an ordinary within an arrow // `ordinary_0` --> `arrow_3` +/// * an arrow within an ordinary // `arrow_2` --> `ordinary_2` +/// * an arrow within an arrow // `arrow_1` --> `arrow_3` +/// +/// And also test top level `toString()` workability. +/// +/// It use `format!(..)` to avoid copy-past & human mistakes +#[test] +fn test_ordinary_and_arrow_to_string() { + let ordinary_0 = "function oa3(yy, xx) { + return xx * 3 + yy + }"; + let arrow_1 = r#"(b) => b * 2"#; + let arrow_2 = r#"(b) => b * /* --- */ 4"#; + let arrow_3 = format!( + r#"(a) => {{ + ia1 = {arrow_1} ; + // \\ // \\ // \\ // \\ // \\ // \\ // \\ + A = ia1 . toString( ); + {ordinary_0} + A1 = oa3.toString( ); + return ia1(a) - 1 + }}"# + ); + + let ordinary_1 = r#"function oa1(yy, xx) { + return xx * 3 + yy + }"#; + let ordinary_2 = format!( + "function oa2(a, boba) {{ + {ordinary_1} + B = oa1.toString();;; + C = oa2.toString(); + ia2 = {arrow_2}; + D = ia2.toString(); + return oa1(a, 5) - boba; + }}" + ); + + let code = format!( + "unused_f = (b) => b * b ; + var A; + var A1; + var B; + var C; + var D; + var E; + var F; + + ia3 = {arrow_3}; + + {ordinary_2} + + oa2(7, 2); + ia3(7); + + const x = {{ + a: A, + a1: A1, + b: B, + c: C, + d: D, + e: ia3.toString(), + f: oa2.toString(), + }}; + + x" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + a1: String, + b: String, + c: String, + d: String, + e: String, + f: String, + } + let expected = Expected { + a: arrow_1.into(), + a1: ordinary_0.into(), + b: ordinary_1.into(), + c: ordinary_2.clone(), + d: arrow_2.into(), + e: arrow_3, + f: ordinary_2, + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_simple_generator_to_string() { + let inner_fn = "function ff(yy, xx) { + return xx * 3 + yy + }"; + let generator = format!( + "function* simpleGenerator() {{ + {inner_fn} + A = ff.toString(); + + yield ff(1, 1); + yield ff(2, 2); + yield ff(3, 3); + }}" + ); + + let code = format!( + " + var A; + var B; + + {generator} + B = simpleGenerator.toString(); + + const gen = simpleGenerator(); + gen.next(); + gen.next(); + + const x = {{ + a: A, + b: B, + }}; + x" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + b: String, + } + let expected = Expected { + a: inner_fn.into(), + b: generator, + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_async_fn_to_string() { + let f = "function resolveAfter2Seconds(x) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(x); + }, 2000); + }); + }"; + let async_fn = "async function asyncCall() { + let r = await resolveAfter2Seconds(88); + return r + }"; + + let code = format!( + " + {f} + {async_fn} + const x = {{ + a: asyncCall.toString(), + }}; + x" + ); + + let expected = ExpectedOne { a: async_fn.into() }; + assert_helper(&code, expected); +} + +#[test] +fn test_async_generator_to_string() { + let async_gen = "async function* asyncGenerator(n) { + let index = 1 ; + while (index <= n ) { + await new Promise /* 0____0 */ (resolve => setTimeout(resolve, 250, index)); + yield index++; + } + }"; + + let code = format!( + " + {async_gen} + const x = {{ + a: asyncGenerator.toString(), + }}; + x" + ); + + let expected = ExpectedOne { + a: async_gen.into(), + }; + assert_helper(&code, expected); +} + +#[test] +fn test_async_arrow_to_string() { + let async_arrow = "async (a, b) => { + return new Promise(resolve => { + setTimeout(() => { + resolve(a + b); + }, 1000); + }); + }"; + let func_expr = "function(a) { + return a /* * */ * 2; + }"; + let async_func_expr = "async function() { + return /* * */ 1; + }"; + let gen_expr = "function*(a) { + yield a /* * */; + yield a + 1 /* * */; + yield a + 2 /* * */; + }"; + let async_gen_expr = "async function*(a) { + yield await a /* * */; + yield await a + 1 /* * */; + yield await a + 2 /* * */; + }"; + + let code = format!( + " + const asyncArrowAdd = {async_arrow}; + const funcExpr = {func_expr}; + const asyncFnExpr = {async_func_expr} ; + const genExpr = {gen_expr} ; + const asyncGenExpr = {async_gen_expr}; + + const x = {{ + a: asyncArrowAdd.toString(), + b: funcExpr.toString(), + c: asyncFnExpr.toString(), + d: genExpr.toString(), + e: asyncGenExpr.toString(), + }}; + x" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + b: String, + c: String, + d: String, + e: String, + } + let expected = Expected { + a: async_arrow.into(), + b: func_expr.into(), + c: async_func_expr.into(), + d: gen_expr.into(), + e: async_gen_expr.into(), + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_class_methods_to_string() { + let calc_area = "calcArea() { + return this.height * this.width; + }"; + let get_area = "get area() { + return this.calcArea(/**/); + }"; + let sides = "*sides() { + yield this.height; + yield this.width; + yield this.height; + yield this.width; + }"; + + let code = format!( + " + class Rectangle {{ + constructor(height, width) {{ + this.height = height; + this.width = width; + }} + {calc_area} + {get_area} + {sides} + }} + + let r = new Rectangle(24, 42); + const descr = Object.getOwnPropertyDescriptor(Rectangle.prototype, 'area'); + const x = {{ + calc_area: r.calcArea.toString(), + sides: r.sides.toString(), + area_val: r.area.toString(), + area: descr.get.toString(), + }}; + x" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + calc_area: String, + sides: String, + area_val: String, + area: String, + } + let expected = Expected { + calc_area: calc_area.into(), + sides: sides.into(), + area_val: (24 * 42).to_string(), + area: get_area.into(), + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_obj_methods_to_string() { + let get_undef = "get p2() { /* / */ }"; + let ordinary = "p3(a, b) { return a + b }"; + let set_p0 = "set p4(a) { /* * */ p0 = a; }"; + let generator = "*vroom_d() { yield 4; }"; + let async_generator = "async* vroom_e() { }"; + + let code = format!( + " + const x = {{ + p0: 97, + p1: true, + {get_undef}, + {ordinary}, + {set_p0}, + {generator}, + {async_generator}, + }}; + + let descr_a = Object.getOwnPropertyDescriptor(x, 'p2'); + let descr_c = Object.getOwnPropertyDescriptor(x, 'p4'); + + const ret = {{ + a: descr_a.get.toString(), + b: x.p3.toString(), + c: descr_c.set.toString(), + d: x.vroom_d.toString(), + e: x.vroom_e.toString(), + }}; + ret" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + b: String, + c: String, + d: String, + e: String, + } + let expected = Expected { + a: get_undef.into(), + b: ordinary.into(), + c: set_p0.into(), + d: generator.into(), + e: async_generator.into(), + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_eval_fn_to_string() { + let function_def = "function f1(x) { + return 1 + x * x + }"; + let code = format!( + " + eval(`{function_def}; 42`); + + const ret = {{ a: f1.toString() }}; + ret" + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + } + let expected = Expected { + a: function_def.into(), + }; + + assert_helper(&code, expected); +} + +#[test] +fn test_static_to_string() { + let fn_def = "function f3(x) { + return 3 + x * x + 3 + }"; + let static_fn_def = format!( + "staticFunc() {{ + {fn_def};;; + return f3 . toString() + }}" + ); + let fn_in_static_block = "function ff(x) { return \"f\" + \"f\" }"; + + let code = format!( + " + class TestClass {{ + static {static_fn_def} + + static str = \"\"; + static {{ + {fn_in_static_block}; + this.str = ff.toString(); + }} + }} + + const ret = {{ + a: TestClass.staticFunc(), + b: TestClass.staticFunc.toString(), + c: TestClass.str, + }}; + ret + " + ); + + #[derive(Debug, TryFromJs, PartialEq, Eq)] + struct Expected { + a: String, + b: String, + c: String, + } + let expected = Expected { + a: fn_def.into(), + b: static_fn_def, + c: fn_in_static_block.into(), + }; + + assert_helper(&code, expected); +} + +#[derive(Debug, TryFromJs, PartialEq, Eq)] +struct ExpectedOne { + a: String, +} + +#[allow(clippy::needless_pass_by_value)] +fn assert_helper(code: &str, expected: T) +where + T: TryFromJs + Debug + Eq, +{ + let context = &mut Context::default(); + let js = context.eval(Source::from_bytes(code)).unwrap(); + let res = T::try_from_js(&js, context).unwrap(); + assert_eq!(expected, res); +} diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 273607ad438..a9be0142a9e 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -8,7 +8,7 @@ use crate::{ OrdinaryObject, }, object::JsObject, - Context, JsBigInt, JsString, JsValue, + Context, JsBigInt, JsString, JsValue, SpannedSourceText, }; use bitflags::bitflags; use boa_ast::scope::{BindingLocator, Scope}; @@ -166,6 +166,9 @@ pub struct CodeBlock { /// inline caching pub(crate) ic: Box<[InlineCache]>, + + /// source text of the code block + pub(crate) source_text_spanned: SpannedSourceText, } /// ---- `CodeBlock` public API ---- @@ -189,6 +192,7 @@ impl CodeBlock { parameter_length: 0, handlers: ThinVec::default(), ic: Box::default(), + source_text_spanned: SpannedSourceText::new_empty(), } } diff --git a/core/parser/src/error/mod.rs b/core/parser/src/error/mod.rs index 502498234c5..b20092ed002 100644 --- a/core/parser/src/error/mod.rs +++ b/core/parser/src/error/mod.rs @@ -140,13 +140,14 @@ impl Error { } /// Creates a "general" parsing error. - pub(crate) fn general(message: S, position: Position) -> Self + pub(crate) fn general(message: S, position: P) -> Self where S: Into>, + P: Into, { Self::General { message: message.into(), - position, + position: position.into(), } } diff --git a/core/parser/src/lexer/comment.rs b/core/parser/src/lexer/comment.rs index bfb8969c544..8ffbf24a79e 100644 --- a/core/parser/src/lexer/comment.rs +++ b/core/parser/src/lexer/comment.rs @@ -2,7 +2,7 @@ use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Span}; +use boa_ast::PositionGroup; use boa_interner::Interner; use boa_profiler::Profiler; @@ -22,7 +22,7 @@ impl Tokenizer for SingleLineComment { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -39,9 +39,10 @@ impl Tokenizer for SingleLineComment { }; cursor.next_char().expect("Comment character vanished"); } - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::Comment, - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } } @@ -62,7 +63,7 @@ impl Tokenizer for MultiLineComment { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -75,13 +76,14 @@ impl Tokenizer for MultiLineComment { let tried_ch = char::try_from(ch); match tried_ch { Ok(c) if c == '*' && cursor.next_if(0x2F /* / */)? => { - return Ok(Token::new( + return Ok(Token::new_by_position_group( if new_line { TokenKind::LineTerminator } else { TokenKind::Comment }, - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } Ok(c) if c == '\r' || c == '\n' || c == '\u{2028}' || c == '\u{2029}' => { @@ -110,7 +112,7 @@ impl Tokenizer for HashbangComment { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -126,9 +128,10 @@ impl Tokenizer for HashbangComment { }; } - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::Comment, - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } } diff --git a/core/parser/src/lexer/cursor.rs b/core/parser/src/lexer/cursor.rs index ae352621da1..8c3ff84b35e 100644 --- a/core/parser/src/lexer/cursor.rs +++ b/core/parser/src/lexer/cursor.rs @@ -1,7 +1,7 @@ //! Boa's lexer cursor that manages the input byte stream. use crate::source::{ReadChar, UTF8Input}; -use boa_ast::Position; +use boa_ast::{LinearPosition, Position, PositionGroup, SourceText}; use boa_profiler::Profiler; use std::io::{self, Error, ErrorKind}; @@ -13,14 +13,33 @@ pub(super) struct Cursor { module: bool, strict: bool, peeked: [Option; 4], + source_collector: SourceText, } impl Cursor { /// Gets the current position of the cursor in the source code. + #[inline] + pub(super) fn pos_group(&self) -> PositionGroup { + PositionGroup::new(self.pos, self.linear_pos()) + } + + /// Gets the current position of the cursor in the source code. + #[inline] pub(super) const fn pos(&self) -> Position { self.pos } + /// Gets the current linear position of the cursor in the source code. + #[inline] + pub(super) fn linear_pos(&self) -> LinearPosition { + self.source_collector.cur_linear_position() + } + + pub(super) fn take_source(&mut self) -> SourceText { + let replace_with = SourceText::with_capacity(0); + std::mem::replace(&mut self.source_collector, replace_with) + } + /// Advances the position to the next column. fn next_column(&mut self) { let current_line = self.pos.line_number(); @@ -65,6 +84,7 @@ impl Cursor { strict: false, module: false, peeked: [None; 4], + source_collector: SourceText::default(), } } @@ -184,6 +204,10 @@ impl Cursor { self.iter.next_char()? }; + if let Some(ch) = ch { + self.source_collector.collect_code_point(ch); + } + match ch { Some(0xD) => { // Try to take a newline if it's next, for windows "\r\n" newlines @@ -191,6 +215,7 @@ impl Cursor { if self.peek_char()? == Some(0xA) { self.peeked[0] = None; self.peeked.rotate_left(1); + self.source_collector.collect_code_point(0xA); } self.next_line(); } diff --git a/core/parser/src/lexer/identifier.rs b/core/parser/src/lexer/identifier.rs index 35ac699dbbb..34f35d9a654 100644 --- a/core/parser/src/lexer/identifier.rs +++ b/core/parser/src/lexer/identifier.rs @@ -4,7 +4,7 @@ use crate::lexer::{ token::ContainsEscapeSequence, Cursor, Error, StringLiteral, Token, TokenKind, Tokenizer, }; use crate::source::ReadChar; -use boa_ast::{Position, Span}; +use boa_ast::PositionGroup; use boa_interner::Interner; use boa_profiler::Profiler; @@ -56,7 +56,7 @@ impl Tokenizer for Identifier { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -84,14 +84,18 @@ impl Tokenizer for Identifier { )), }; - Ok(Token::new(token_kind, Span::new(start_pos, cursor.pos()))) + Ok(Token::new_by_position_group( + token_kind, + start_pos, + cursor.pos_group(), + )) } } impl Identifier { pub(super) fn take_identifier_name( cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, init: char, ) -> Result<(String, bool), Error> where @@ -101,7 +105,7 @@ impl Identifier { let mut contains_escaped_chars = false; let mut identifier_name = if init == '\\' && cursor.next_if(0x75 /* u */)? { - let ch = StringLiteral::take_unicode_escape_sequence(cursor, start_pos)?; + let ch = StringLiteral::take_unicode_escape_sequence(cursor, start_pos.position())?; if Self::is_identifier_start(ch) { contains_escaped_chars = true; @@ -110,7 +114,7 @@ impl Identifier { .expect("all identifier starts must be convertible to strings"), ) } else { - return Err(Error::Syntax("invalid identifier start".into(), start_pos)); + return Err(Error::syntax("invalid identifier start", start_pos)); } } else { // The caller guarantees that `init` is a valid identifier start diff --git a/core/parser/src/lexer/mod.rs b/core/parser/src/lexer/mod.rs index 8a69e409318..a95fb004d8c 100644 --- a/core/parser/src/lexer/mod.rs +++ b/core/parser/src/lexer/mod.rs @@ -42,7 +42,7 @@ use self::{ template::TemplateLiteral, }; use crate::source::{ReadChar, UTF8Input}; -use boa_ast::{Position, Punctuator, Span}; +use boa_ast::{PositionGroup, Punctuator}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -56,7 +56,7 @@ trait Tokenizer { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -123,7 +123,7 @@ impl Lexer { /// As per pub(crate) fn lex_slash_token( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, init_with_eq: bool, ) -> Result @@ -157,14 +157,16 @@ impl Lexer { // Consume the '=' self.cursor.next_char()?.expect("= token vanished"); } - Ok(Token::new( + Ok(Token::new_by_position_group( Punctuator::AssignDiv.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )) } else { - Ok(Token::new( + Ok(Token::new_by_position_group( Punctuator::Div.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )) } } @@ -176,9 +178,10 @@ impl Lexer { } } } else { - Ok(Token::new( + Ok(Token::new_by_position_group( Punctuator::Div.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )) } } @@ -202,7 +205,7 @@ impl Lexer { let _next = self.cursor.next_char(); let _next = self.cursor.next_char(); - let start = self.cursor.pos(); + let start = self.cursor.pos_group(); SingleLineComment.lex(&mut self.cursor, start, interner)?; } @@ -221,7 +224,7 @@ impl Lexer { { let _timer = Profiler::global().start_event("next()", "Lexing"); - let mut start = self.cursor.pos(); + let mut start = self.cursor.pos_group(); let Some(mut next_ch) = self.cursor.next_char()? else { return Ok(None); }; @@ -239,7 +242,7 @@ impl Lexer { // Ignore whitespace if is_whitespace(next_ch) { loop { - start = self.cursor.pos(); + start = self.cursor.pos_group(); let Some(next) = self.cursor.next_char()? else { return Ok(None); }; @@ -252,19 +255,22 @@ impl Lexer { if let Ok(c) = char::try_from(next_ch) { let token = match c { - '\r' | '\n' | '\u{2028}' | '\u{2029}' => Ok(Token::new( + '\r' | '\n' | '\u{2028}' | '\u{2029}' => Ok(Token::new_by_position_group( TokenKind::LineTerminator, - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), '"' | '\'' => StringLiteral::new(c).lex(&mut self.cursor, start, interner), '`' => TemplateLiteral.lex(&mut self.cursor, start, interner), - ';' => Ok(Token::new( + ';' => Ok(Token::new_by_position_group( Punctuator::Semicolon.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - ':' => Ok(Token::new( + ':' => Ok(Token::new_by_position_group( Punctuator::Colon.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), '.' => { if self @@ -278,33 +284,40 @@ impl Lexer { SpreadLiteral::new().lex(&mut self.cursor, start, interner) } } - '(' => Ok(Token::new( + '(' => Ok(Token::new_by_position_group( Punctuator::OpenParen.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - ')' => Ok(Token::new( + ')' => Ok(Token::new_by_position_group( Punctuator::CloseParen.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - ',' => Ok(Token::new( + ',' => Ok(Token::new_by_position_group( Punctuator::Comma.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - '{' => Ok(Token::new( + '{' => Ok(Token::new_by_position_group( Punctuator::OpenBlock.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - '}' => Ok(Token::new( + '}' => Ok(Token::new_by_position_group( Punctuator::CloseBlock.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - '[' => Ok(Token::new( + '[' => Ok(Token::new_by_position_group( Punctuator::OpenBracket.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), - ']' => Ok(Token::new( + ']' => Ok(Token::new_by_position_group( Punctuator::CloseBracket.into(), - Span::new(start, self.cursor.pos()), + start, + self.cursor.pos_group(), )), '#' => PrivateIdentifier::new().lex(&mut self.cursor, start, interner), '/' => self.lex_slash_token(start, interner, false), @@ -316,7 +329,7 @@ impl Lexer { let _next = self.cursor.next_char(); let _next = self.cursor.next_char(); let _next = self.cursor.next_char(); - let start = self.cursor.pos(); + let start = self.cursor.pos_group(); SingleLineComment.lex(&mut self.cursor, start, interner) } #[allow(clippy::cast_possible_truncation)] @@ -339,7 +352,7 @@ impl Lexer { start.line_number(), start.column_number() ); - Err(Error::syntax(details, start)) + Err(Error::syntax(details, start.position())) } }?; @@ -351,7 +364,7 @@ impl Lexer { start.line_number(), start.column_number() ), - start, + start.position(), )) } } @@ -381,7 +394,7 @@ impl Lexer { /// Performs the lexing of a template literal. pub(crate) fn lex_template( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, ) -> Result where @@ -389,6 +402,10 @@ impl Lexer { { TemplateLiteral.lex(&mut self.cursor, start, interner) } + + pub(super) fn take_source(&mut self) -> boa_ast::SourceText { + self.cursor.take_source() + } } impl<'a> From<&'a [u8]> for Lexer> { diff --git a/core/parser/src/lexer/number.rs b/core/parser/src/lexer/number.rs index 787392001bb..ac734f0ad39 100644 --- a/core/parser/src/lexer/number.rs +++ b/core/parser/src/lexer/number.rs @@ -2,7 +2,7 @@ use crate::lexer::{token::Numeric, Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Span}; +use boa_ast::PositionGroup; use boa_interner::Interner; use boa_profiler::Profiler; use num_bigint::BigInt; @@ -185,7 +185,7 @@ impl Tokenizer for NumberLiteral { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -260,9 +260,10 @@ impl Tokenizer for NumberLiteral { cursor.next_char()?.expect("n character vanished"); // DecimalBigIntegerLiteral '0n' - return Ok(Token::new( + return Ok(Token::new_by_position_group( TokenKind::NumericLiteral(Numeric::BigInt(BigInt::zero().into())), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )); } byte => { @@ -309,9 +310,10 @@ impl Tokenizer for NumberLiteral { } else { // DecimalLiteral lexing. // Indicates that the number is just a single 0. - return Ok(Token::new( + return Ok(Token::new_by_position_group( TokenKind::NumericLiteral(Numeric::Integer(0)), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )); } } @@ -423,9 +425,10 @@ impl Tokenizer for NumberLiteral { } }; - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::NumericLiteral(num), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } } diff --git a/core/parser/src/lexer/operator.rs b/core/parser/src/lexer/operator.rs index 130a216b9ef..576aab2acc3 100644 --- a/core/parser/src/lexer/operator.rs +++ b/core/parser/src/lexer/operator.rs @@ -2,7 +2,7 @@ use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Punctuator, Span}; +use boa_ast::{PositionGroup, Punctuator}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -41,16 +41,16 @@ macro_rules! vop { /// The `op` macro handles binary operations or assignment operations and converts them into tokens. macro_rules! op { ($cursor:ident, $start_pos:expr, $assign_op:expr, $op:expr) => ({ - Token::new( + Token::new_by_position_group( vop!($cursor, $assign_op, $op).into(), - Span::new($start_pos, $cursor.pos()), + $start_pos, $cursor.pos_group(), ) }); ($cursor:ident, $start_pos:expr, $assign_op:expr, $op:expr, {$($case:pat => $block:expr),+}) => ({ let punc: Punctuator = vop!($cursor, $assign_op, $op, {$($case => $block),+}); - Token::new( + Token::new_by_position_group( punc.into(), - Span::new($start_pos, $cursor.pos()), + $start_pos, $cursor.pos_group(), ) }); } @@ -81,7 +81,7 @@ impl Tokenizer for Operator { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -121,14 +121,16 @@ impl Tokenizer for Operator { Some(0x2E /* . */) if !matches!(second, Some(second) if (0x30..=0x39 /* 0..=9 */).contains(&second)) => { cursor.next_char()?.expect(". vanished"); - Token::new( + Token::new_by_position_group( TokenKind::Punctuator(Punctuator::Optional), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), ) } - _ => Token::new( + _ => Token::new_by_position_group( TokenKind::Punctuator(Punctuator::Question), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), ), } } @@ -160,7 +162,9 @@ impl Tokenizer for Operator { vop!(cursor, Punctuator::StrictNotEq, Punctuator::NotEq), Punctuator::Not ), - b'~' => Token::new(Punctuator::Neg.into(), Span::new(start_pos, cursor.pos())), + b'~' => { + Token::new_by_position_group(Punctuator::Neg.into(), start_pos, cursor.pos_group()) + } op => unimplemented!("operator {}", op), }) } diff --git a/core/parser/src/lexer/private_identifier.rs b/core/parser/src/lexer/private_identifier.rs index ce0f1eba4b1..0208b329171 100644 --- a/core/parser/src/lexer/private_identifier.rs +++ b/core/parser/src/lexer/private_identifier.rs @@ -2,7 +2,7 @@ use crate::lexer::{identifier::Identifier, Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Span}; +use boa_ast::PositionGroup; use boa_interner::Interner; use boa_profiler::Profiler; @@ -26,7 +26,7 @@ impl Tokenizer for PrivateIdentifier { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -39,16 +39,18 @@ impl Tokenizer for PrivateIdentifier { match c { '\\' if cursor.peek_char()? == Some(0x0075 /* u */) => { let (name, _) = Identifier::take_identifier_name(cursor, start_pos, c)?; - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::PrivateIdentifier(interner.get_or_intern(name.as_str())), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } _ if Identifier::is_identifier_start(c as u32) => { let (name, _) = Identifier::take_identifier_name(cursor, start_pos, c)?; - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::PrivateIdentifier(interner.get_or_intern(name.as_str())), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } _ => Err(Error::syntax( diff --git a/core/parser/src/lexer/regex.rs b/core/parser/src/lexer/regex.rs index e54d2893487..c595d0a20f9 100644 --- a/core/parser/src/lexer/regex.rs +++ b/core/parser/src/lexer/regex.rs @@ -1,9 +1,9 @@ //! Boa's lexing for ECMAScript regex literals. -use crate::lexer::{Cursor, Error, Span, Token, TokenKind, Tokenizer}; +use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; use bitflags::bitflags; -use boa_ast::Position; +use boa_ast::{Position, PositionGroup}; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; use regress::{Flags, Regex}; @@ -40,7 +40,7 @@ impl Tokenizer for RegexLiteral { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -143,18 +143,19 @@ impl Tokenizer for RegexLiteral { } if let Err(error) = Regex::from_unicode(body.into_iter(), flags_str) { - return Err(Error::Syntax( - format!("Invalid regular expression literal: {error}").into(), + return Err(Error::syntax( + format!("Invalid regular expression literal: {error}"), start_pos, )); } - Ok(Token::new( + Ok(Token::new_by_position_group( TokenKind::regular_expression_literal( interner.get_or_intern(body_utf16.as_slice()), parse_regex_flags(flags_str, flags_start, interner)?, ), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } } diff --git a/core/parser/src/lexer/spread.rs b/core/parser/src/lexer/spread.rs index 5732a50f9a5..b03a0a3ad8d 100644 --- a/core/parser/src/lexer/spread.rs +++ b/core/parser/src/lexer/spread.rs @@ -2,7 +2,7 @@ use crate::lexer::{Cursor, Error, Token, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Punctuator, Span}; +use boa_ast::{PositionGroup, Punctuator}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -30,7 +30,7 @@ impl Tokenizer for SpreadLiteral { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, _interner: &mut Interner, ) -> Result where @@ -41,9 +41,10 @@ impl Tokenizer for SpreadLiteral { // . or ... if cursor.next_if(0x2E /* . */)? { if cursor.next_if(0x2E /* . */)? { - Ok(Token::new( + Ok(Token::new_by_position_group( Punctuator::Spread.into(), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } else { Err(Error::syntax( @@ -52,9 +53,10 @@ impl Tokenizer for SpreadLiteral { )) } } else { - Ok(Token::new( + Ok(Token::new_by_position_group( Punctuator::Dot.into(), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )) } } diff --git a/core/parser/src/lexer/string.rs b/core/parser/src/lexer/string.rs index 40861585f8d..59835180abf 100644 --- a/core/parser/src/lexer/string.rs +++ b/core/parser/src/lexer/string.rs @@ -2,7 +2,7 @@ use crate::lexer::{token::EscapeSequence, Cursor, Error, Token, TokenKind, Tokenizer}; use crate::source::ReadChar; -use boa_ast::{Position, Span}; +use boa_ast::{LinearSpan, Position, PositionGroup, Span}; use boa_interner::Interner; use boa_profiler::Profiler; use std::io::{self, ErrorKind}; @@ -79,7 +79,7 @@ impl Tokenizer for StringLiteral { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -87,12 +87,17 @@ impl Tokenizer for StringLiteral { { let _timer = Profiler::global().start_event("StringLiteral", "Lexing"); - let (lit, span, escape_sequence) = - Self::take_string_characters(cursor, start_pos, self.terminator, cursor.strict())?; + let (lit, span, escape_sequence) = Self::take_string_characters( + cursor, + start_pos.position(), + self.terminator, + cursor.strict(), + )?; Ok(Token::new( TokenKind::string_literal(interner.get_or_intern(&lit[..]), escape_sequence), span, + LinearSpan::new(start_pos.linear_position(), cursor.linear_pos()), )) } } diff --git a/core/parser/src/lexer/template.rs b/core/parser/src/lexer/template.rs index 25aecc3f82a..a65121b80f0 100644 --- a/core/parser/src/lexer/template.rs +++ b/core/parser/src/lexer/template.rs @@ -4,7 +4,7 @@ use crate::{ lexer::{string::UTF16CodeUnitsBuffer, Cursor, Error, Token, TokenKind, Tokenizer}, source::ReadChar, }; -use boa_ast::{Position, Span}; +use boa_ast::PositionGroup; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; use std::io::{self, ErrorKind}; @@ -185,7 +185,7 @@ impl Tokenizer for TemplateLiteral { fn lex( &mut self, cursor: &mut Cursor, - start_pos: Position, + start_pos: PositionGroup, interner: &mut Interner, ) -> Result where @@ -208,9 +208,10 @@ impl Tokenizer for TemplateLiteral { let raw_sym = interner.get_or_intern(&buf[..]); let template_string = TemplateString::new(raw_sym, interner); - return Ok(Token::new( + return Ok(Token::new_by_position_group( TokenKind::template_no_substitution(template_string), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )); } // $ @@ -218,9 +219,10 @@ impl Tokenizer for TemplateLiteral { let raw_sym = interner.get_or_intern(&buf[..]); let template_string = TemplateString::new(raw_sym, interner); - return Ok(Token::new( + return Ok(Token::new_by_position_group( TokenKind::template_middle(template_string), - Span::new(start_pos, cursor.pos()), + start_pos, + cursor.pos_group(), )); } // \ diff --git a/core/parser/src/lexer/tests.rs b/core/parser/src/lexer/tests.rs index c0a7b6a0e5c..35cf36dd8d2 100644 --- a/core/parser/src/lexer/tests.rs +++ b/core/parser/src/lexer/tests.rs @@ -3,10 +3,10 @@ use crate::lexer::{ template::TemplateString, token::{ContainsEscapeSequence, EscapeSequence, Numeric}, - Cursor, Error, Interner, Lexer, Position, Punctuator, Span, TokenKind, + Cursor, Error, Interner, Lexer, Punctuator, TokenKind, }; use crate::source::ReadChar; -use boa_ast::Keyword; +use boa_ast::{Keyword, Position, Span}; use boa_interner::Sym; use boa_macros::utf16; use std::str; diff --git a/core/parser/src/lexer/token.rs b/core/parser/src/lexer/token.rs index 8da0520b6de..d719853b966 100644 --- a/core/parser/src/lexer/token.rs +++ b/core/parser/src/lexer/token.rs @@ -7,7 +7,7 @@ use crate::lexer::template::TemplateString; use bitflags::bitflags; -use boa_ast::{Keyword, Punctuator, Span}; +use boa_ast::{Keyword, LinearSpan, PositionGroup, Punctuator, Span}; use boa_interner::{Interner, Sym}; use num_bigint::BigInt; @@ -23,14 +23,35 @@ pub struct Token { kind: TokenKind, /// The token position in the original source code. span: Span, + /// The token linear position in the original source code. + linear_span: LinearSpan, } impl Token { + /// Create a new detailed token from the token data, line number, column number, and linear position + #[inline] + #[must_use] + pub const fn new(kind: TokenKind, span: Span, linear_span: LinearSpan) -> Self { + Self { + kind, + span, + linear_span, + } + } + /// Create a new detailed token from the token data, line number and column number #[inline] #[must_use] - pub const fn new(kind: TokenKind, span: Span) -> Self { - Self { kind, span } + pub fn new_by_position_group( + kind: TokenKind, + start: PositionGroup, + end: PositionGroup, + ) -> Self { + Self::new( + kind, + Span::new(start.position(), end.position()), + LinearSpan::new(start.linear_position(), end.linear_position()), + ) } /// Gets the kind of the token. @@ -47,6 +68,20 @@ impl Token { self.span } + /// Gets the starting position group of the token. + #[inline] + #[must_use] + pub const fn start_group(&self) -> PositionGroup { + PositionGroup::new(self.span.start(), self.linear_span.start()) + } + + /// Gets the token span in the original source code. + #[inline] + #[must_use] + pub const fn linear_span(&self) -> LinearSpan { + self.linear_span + } + /// Converts the token to a `String`. #[inline] pub(crate) fn to_string(&self, interner: &Interner) -> String { diff --git a/core/parser/src/parser/cursor/buffered_lexer/mod.rs b/core/parser/src/parser/cursor/buffered_lexer/mod.rs index 8c9735eda8f..274a7143527 100644 --- a/core/parser/src/parser/cursor/buffered_lexer/mod.rs +++ b/core/parser/src/parser/cursor/buffered_lexer/mod.rs @@ -4,7 +4,7 @@ use crate::{ source::{ReadChar, UTF8Input}, Error, }; -use boa_ast::Position; +use boa_ast::{LinearPosition, PositionGroup}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -30,6 +30,7 @@ pub(super) struct BufferedLexer { peeked: [Option; PEEK_BUF_SIZE], read_index: usize, write_index: usize, + last_linear_pos: LinearPosition, } impl From> for BufferedLexer @@ -52,6 +53,7 @@ where ], read_index: 0, write_index: 0, + last_linear_pos: LinearPosition::default(), } } } @@ -85,7 +87,7 @@ where /// If `init_with_eq` is `true`, then assuming that the starting '/=' has already been consumed. pub(super) fn lex_regex( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, init_with_eq: bool, ) -> ParseResult { @@ -100,7 +102,7 @@ where /// '}' has already been consumed. pub(super) fn lex_template( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, ) -> ParseResult { self.lexer @@ -202,6 +204,10 @@ where let tok = self.peeked[self.read_index].take(); self.read_index = (self.read_index + 1) % PEEK_BUF_SIZE; + if let Some(tok) = &tok { + self.last_linear_pos = tok.linear_span().end(); + } + Ok(tok) } else { // We do not update the read index, since we should always return `None` from now on. @@ -265,4 +271,14 @@ where Ok(res_token) } + + /// Gets current linear position in the source code. + #[inline] + pub(super) fn linear_pos(&self) -> LinearPosition { + self.last_linear_pos + } + + pub(super) fn take_source(&mut self) -> boa_ast::SourceText { + self.lexer.take_source() + } } diff --git a/core/parser/src/parser/cursor/mod.rs b/core/parser/src/parser/cursor/mod.rs index c6d0160bfe3..ca120d70f2a 100644 --- a/core/parser/src/parser/cursor/mod.rs +++ b/core/parser/src/parser/cursor/mod.rs @@ -7,7 +7,7 @@ use crate::{ source::ReadChar, Error, }; -use boa_ast::{Position, Punctuator}; +use boa_ast::{LinearPosition, PositionGroup, Punctuator}; use boa_interner::Interner; use buffered_lexer::BufferedLexer; @@ -72,7 +72,7 @@ where /// If `init_with_eq` is `true`, then assuming that the starting '/=' has already been consumed. pub(super) fn lex_regex( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, init_with_eq: bool, ) -> ParseResult { @@ -81,7 +81,7 @@ where pub(super) fn lex_template( &mut self, - start: Position, + start: PositionGroup, interner: &mut Interner, ) -> ParseResult { self.buffered_lexer.lex_template(start, interner) @@ -304,4 +304,14 @@ where None }) } + + /// Gets current linear position in the source code. + #[inline] + pub(super) fn linear_pos(&self) -> LinearPosition { + self.buffered_lexer.linear_pos() + } + + pub(super) fn take_source(&mut self) -> boa_ast::SourceText { + self.buffered_lexer.take_source() + } } diff --git a/core/parser/src/parser/expression/assignment/arrow_function.rs b/core/parser/src/parser/expression/assignment/arrow_function.rs index dbf4ec45600..448a4a5b0cf 100644 --- a/core/parser/src/parser/expression/assignment/arrow_function.rs +++ b/core/parser/src/parser/expression/assignment/arrow_function.rs @@ -71,6 +71,7 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("ArrowFunction", "Parsing"); let next_token = cursor.peek(0, interner).or_abrupt()?; + let start_linear_span = next_token.linear_span(); let (params, params_start_position) = if next_token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { @@ -153,7 +154,10 @@ where interner, )?; - Ok(ast::function::ArrowFunction::new(None, params, body)) + let linear_pos_end = body.linear_pos_end(); + let span = start_linear_span.union(linear_pos_end); + + Ok(ast::function::ArrowFunction::new(None, params, body, span)) } } @@ -196,6 +200,7 @@ where .into(), )) .into()], + cursor.linear_pos(), false, ), }; diff --git a/core/parser/src/parser/expression/assignment/async_arrow_function.rs b/core/parser/src/parser/expression/assignment/async_arrow_function.rs index db8a4940cf5..c004b899cab 100644 --- a/core/parser/src/parser/expression/assignment/async_arrow_function.rs +++ b/core/parser/src/parser/expression/assignment/async_arrow_function.rs @@ -69,7 +69,9 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("AsyncArrowFunction", "Parsing"); - cursor.expect((Keyword::Async, false), "async arrow function", interner)?; + let async_token = + cursor.expect((Keyword::Async, false), "async arrow function", interner)?; + let start_linear_span = async_token.linear_span(); cursor.peek_expect_no_lineterminator(0, "async arrow function", interner)?; let next_token = cursor.peek(0, interner).or_abrupt()?; @@ -144,7 +146,12 @@ where interner, )?; - Ok(ast::function::AsyncArrowFunction::new(None, params, body)) + let linear_pos_end = body.linear_pos_end(); + let span = start_linear_span.union(linear_pos_end); + + Ok(ast::function::AsyncArrowFunction::new( + None, params, body, span, + )) } } @@ -187,6 +194,7 @@ where .into(), )) .into()], + cursor.linear_pos(), false, ), }; diff --git a/core/parser/src/parser/expression/assignment/mod.rs b/core/parser/src/parser/expression/assignment/mod.rs index 564baaa05c9..49a300bd77e 100644 --- a/core/parser/src/parser/expression/assignment/mod.rs +++ b/core/parser/src/parser/expression/assignment/mod.rs @@ -154,7 +154,9 @@ where cursor.set_goal(InputElement::Div); - let position = cursor.peek(0, interner).or_abrupt()?.span().start(); + let peek_token = cursor.peek(0, interner).or_abrupt()?; + let position = peek_token.span().start(); + let start_linear_span = peek_token.linear_span(); let mut lhs = ConditionalExpression::new(self.allow_in, self.allow_yield, self.allow_await) .parse(cursor, interner)?; @@ -216,7 +218,10 @@ where interner, )?; - return Ok(boa_ast::function::ArrowFunction::new(None, parameters, body).into()); + let linear_pos_end = body.linear_pos_end(); + let span = start_linear_span.union(linear_pos_end); + + return Ok(boa_ast::function::ArrowFunction::new(None, parameters, body, span).into()); } // Review if we are trying to assign to an invalid left hand side expression. diff --git a/core/parser/src/parser/expression/left_hand_side/call.rs b/core/parser/src/parser/expression/left_hand_side/call.rs index c17a79391d6..aad09aab31d 100644 --- a/core/parser/src/parser/expression/left_hand_side/call.rs +++ b/core/parser/src/parser/expression/left_hand_side/call.rs @@ -176,7 +176,7 @@ where lhs = TaggedTemplateLiteral::new( self.allow_yield, self.allow_await, - tok.span().start(), + tok.start_group(), lhs, ) .parse(cursor, interner)? diff --git a/core/parser/src/parser/expression/left_hand_side/member.rs b/core/parser/src/parser/expression/left_hand_side/member.rs index 029c220621e..9b98065de5e 100644 --- a/core/parser/src/parser/expression/left_hand_side/member.rs +++ b/core/parser/src/parser/expression/left_hand_side/member.rs @@ -268,7 +268,7 @@ where lhs = TaggedTemplateLiteral::new( self.allow_yield, self.allow_await, - tok.span().start(), + tok.start_group(), lhs, ) .parse(cursor, interner)? diff --git a/core/parser/src/parser/expression/left_hand_side/template.rs b/core/parser/src/parser/expression/left_hand_side/template.rs index 5dbe6fb4d80..50dca50f6b9 100644 --- a/core/parser/src/parser/expression/left_hand_side/template.rs +++ b/core/parser/src/parser/expression/left_hand_side/template.rs @@ -7,7 +7,7 @@ use crate::{ source::ReadChar, Error, }; -use boa_ast::{self as ast, expression::TaggedTemplate, Position, Punctuator}; +use boa_ast::{self as ast, expression::TaggedTemplate, PositionGroup, Punctuator}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -21,7 +21,7 @@ use boa_profiler::Profiler; pub(super) struct TaggedTemplateLiteral { allow_yield: AllowYield, allow_await: AllowAwait, - start: Position, + start: PositionGroup, tag: ast::Expression, } @@ -30,7 +30,7 @@ impl TaggedTemplateLiteral { pub(super) fn new( allow_yield: Y, allow_await: A, - start: Position, + start: PositionGroup, tag: ast::Expression, ) -> Self where diff --git a/core/parser/src/parser/expression/primary/async_function_expression/mod.rs b/core/parser/src/parser/expression/primary/async_function_expression/mod.rs index 0458d169619..4ac2ce2a286 100644 --- a/core/parser/src/parser/expression/primary/async_function_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/async_function_expression/mod.rs @@ -45,6 +45,13 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("AsyncFunctionExpression", "Parsing"); + let token = cursor.expect( + (Keyword::Async, false), + "async function expression", + interner, + )?; + let start_linear_span = token.linear_span(); + cursor.peek_expect_no_lineterminator(0, "async function expression", interner)?; cursor.expect( (Keyword::Function, false), @@ -139,7 +146,9 @@ where interner, )?; - let function = AsyncFunctionExpressionNode::new(name, params, body, name.is_some()); + let span = start_linear_span.union(body.linear_pos_end()); + + let function = AsyncFunctionExpressionNode::new(name, params, body, span, name.is_some()); if contains(&function, ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( diff --git a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs index b2b7da43ef5..3d0b09a8266 100644 --- a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs @@ -9,6 +9,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + /// Checks async expression parsing. #[test] fn check_async_expression() { @@ -30,8 +34,10 @@ fn check_async_expression() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), @@ -77,8 +83,10 @@ fn check_nested_async_expression() { Literal::from(1).into(), ))) .into()], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), @@ -88,8 +96,10 @@ fn check_nested_async_expression() { .unwrap(), )) .into()], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), diff --git a/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs b/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs index b904cc61a93..cc55d878228 100644 --- a/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/async_generator_expression/mod.rs @@ -53,6 +53,12 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("AsyncGeneratorExpression", "Parsing"); + let token = cursor.expect( + (Keyword::Async, false), + "async function expression", + interner, + )?; + let start_linear_span = token.linear_span(); cursor.peek_expect_no_lineterminator(0, "async generator expression", interner)?; cursor.expect( @@ -176,7 +182,9 @@ where interner, )?; - let function = AsyncGeneratorExpressionNode::new(name, params, body, name.is_some()); + let span = start_linear_span.union(body.linear_pos_end()); + + let function = AsyncGeneratorExpressionNode::new(name, params, body, span, name.is_some()); if contains(&function, ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( diff --git a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs index fc967459ea9..86b3011910b 100644 --- a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs @@ -9,6 +9,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + ///checks async generator expression parsing #[test] @@ -31,8 +35,10 @@ fn check_async_generator_expr() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), @@ -77,8 +83,10 @@ fn check_nested_async_generator_expr() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), @@ -88,8 +96,10 @@ fn check_nested_async_generator_expr() { .unwrap(), )) .into()], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), diff --git a/core/parser/src/parser/expression/primary/function_expression/mod.rs b/core/parser/src/parser/expression/primary/function_expression/mod.rs index 88327a79104..d285e47e67f 100644 --- a/core/parser/src/parser/expression/primary/function_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/function_expression/mod.rs @@ -55,7 +55,11 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("FunctionExpression", "Parsing"); + let token = cursor.expect((Keyword::Function, false), "generator expression", interner)?; + let start_linear_span = token.linear_span(); + let token = cursor.peek(0, interner).or_abrupt()?; + let (name, name_span) = match token.kind() { TokenKind::IdentifierName(_) | TokenKind::Keyword(( @@ -134,7 +138,9 @@ where interner, )?; - let function = FunctionExpressionNode::new(name, params, body, name.is_some()); + let span = Some(start_linear_span.union(body.linear_pos_end())); + + let function = FunctionExpressionNode::new(name, params, body, span, name.is_some()); if contains(&function, ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( diff --git a/core/parser/src/parser/expression/primary/function_expression/tests.rs b/core/parser/src/parser/expression/primary/function_expression/tests.rs index 8786bde77a5..fa79619982d 100644 --- a/core/parser/src/parser/expression/primary/function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/function_expression/tests.rs @@ -9,6 +9,8 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + /// Checks async expression parsing. #[test] fn check_function_expression() { @@ -30,8 +32,10 @@ fn check_function_expression() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), ))], + PSEUDO_LINEAR_POS, false, ), + None, false, ) .into(), @@ -76,8 +80,10 @@ fn check_nested_function_expression() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), ))], + PSEUDO_LINEAR_POS, false, ), + None, false, ) .into(), @@ -87,8 +93,10 @@ fn check_nested_function_expression() { .unwrap(), )) .into()], + PSEUDO_LINEAR_POS, false, ), + None, false, ) .into(), @@ -121,8 +129,10 @@ fn check_function_non_reserved_keyword() { ) ) )], + PSEUDO_LINEAR_POS, false, ), + None, true, ) .into(), diff --git a/core/parser/src/parser/expression/primary/generator_expression/mod.rs b/core/parser/src/parser/expression/primary/generator_expression/mod.rs index 66920338a14..454d69c6501 100644 --- a/core/parser/src/parser/expression/primary/generator_expression/mod.rs +++ b/core/parser/src/parser/expression/primary/generator_expression/mod.rs @@ -55,6 +55,9 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("GeneratorExpression", "Parsing"); + let token = cursor.expect((Keyword::Function, false), "generator expression", interner)?; + let start_linear_span = token.linear_span(); + cursor.expect( TokenKind::Punctuator(Punctuator::Mul), "generator expression", @@ -151,7 +154,9 @@ where ))); } - let function = GeneratorExpressionNode::new(name, params, body, name.is_some()); + let span = start_linear_span.union(body.linear_pos_end()); + + let function = GeneratorExpressionNode::new(name, params, body, span, name.is_some()); if contains(&function, ContainsSymbol::Super) { return Err(Error::lex(LexError::Syntax( diff --git a/core/parser/src/parser/expression/primary/generator_expression/tests.rs b/core/parser/src/parser/expression/primary/generator_expression/tests.rs index 6e46f517c18..8d3f9e39eeb 100644 --- a/core/parser/src/parser/expression/primary/generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/generator_expression/tests.rs @@ -8,6 +8,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + #[test] fn check_generator_function_expression() { let interner = &mut Interner::default(); @@ -28,8 +32,10 @@ fn check_generator_function_expression() { [StatementListItem::Statement(Statement::Expression( Expression::from(Yield::new(Some(Literal::from(1).into()), false)), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), @@ -63,8 +69,10 @@ fn check_generator_function_delegate_yield_expression() { [StatementListItem::Statement(Statement::Expression( Expression::from(Yield::new(Some(Literal::from(1).into()), true)), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, false, ) .into(), diff --git a/core/parser/src/parser/expression/primary/mod.rs b/core/parser/src/parser/expression/primary/mod.rs index c96dc22b32d..cdce6c4f8d8 100644 --- a/core/parser/src/parser/expression/primary/mod.rs +++ b/core/parser/src/parser/expression/primary/mod.rs @@ -115,8 +115,7 @@ where Ok(ast::Expression::This) } TokenKind::Keyword((Keyword::Function, _)) => { - cursor.advance(interner); - let next_token = cursor.peek(0, interner).or_abrupt()?; + let next_token = cursor.peek(1, interner).or_abrupt()?; if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { GeneratorExpression::new() .parse(cursor, interner) @@ -152,8 +151,7 @@ where Some(TokenKind::Keyword((Keyword::Function, _))) if !is_line_terminator && !contain_escaped_char => { - cursor.advance(interner); - match cursor.peek(1, interner)?.map(Token::kind) { + match cursor.peek(2, interner)?.map(Token::kind) { Some(TokenKind::Punctuator(Punctuator::Mul)) => { AsyncGeneratorExpression::new() .parse(cursor, interner) @@ -249,9 +247,9 @@ where TokenKind::Punctuator(div @ (Punctuator::Div | Punctuator::AssignDiv)) => { let init_with_eq = div == &Punctuator::AssignDiv; - let position = tok.span().start(); + let start_pos_group = tok.start_group(); cursor.advance(interner); - let tok = cursor.lex_regex(position, interner, init_with_eq)?; + let tok = cursor.lex_regex(start_pos_group, interner, init_with_eq)?; if let TokenKind::RegularExpressionLiteral(body, flags) = *tok.kind() { Ok(AstRegExp::new(body, flags).into()) @@ -274,7 +272,7 @@ where let parser = TemplateLiteral::new( self.allow_yield, self.allow_await, - tok.span().start(), + tok.start_group(), cooked, ); cursor.advance(interner); diff --git a/core/parser/src/parser/expression/primary/object_initializer/mod.rs b/core/parser/src/parser/expression/primary/object_initializer/mod.rs index c2925026270..7215767bd29 100644 --- a/core/parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/core/parser/src/parser/expression/primary/object_initializer/mod.rs @@ -202,6 +202,8 @@ where ); let token = cursor.peek(0, interner).or_abrupt()?; + let start_linear_pos = token.linear_span().start(); + match token.kind() { TokenKind::Keyword((Keyword::Async, true)) if is_keyword => { return Err(Error::general( @@ -243,6 +245,7 @@ where params, body, MethodDefinitionKind::AsyncGenerator, + start_linear_pos, ), )); } @@ -270,6 +273,7 @@ where params, body, MethodDefinitionKind::Async, + start_linear_pos, ), )); } @@ -277,6 +281,7 @@ where } let token = cursor.peek(0, interner).or_abrupt()?; + let start_linear_pos = token.linear_span().start(); if token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { let position = cursor.peek(0, interner).or_abrupt()?.span().start(); @@ -304,6 +309,7 @@ where params, body, MethodDefinitionKind::Generator, + start_linear_pos, ), )); } @@ -387,6 +393,7 @@ where FormalParameterList::default(), body, MethodDefinitionKind::Get, + start_linear_pos, ), )) } @@ -474,6 +481,7 @@ where params, body, MethodDefinitionKind::Set, + start_linear_pos, ), )) } @@ -547,6 +555,7 @@ where params, body, MethodDefinitionKind::Ordinary, + start_linear_pos, ), )) } diff --git a/core/parser/src/parser/expression/primary/object_initializer/tests.rs b/core/parser/src/parser/expression/primary/object_initializer/tests.rs index 4606f18b2e7..63dc136e848 100644 --- a/core/parser/src/parser/expression/primary/object_initializer/tests.rs +++ b/core/parser/src/parser/expression/primary/object_initializer/tests.rs @@ -12,6 +12,8 @@ use boa_ast::{ use boa_interner::{Interner, Sym}; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + /// Checks object literal parsing. #[test] fn check_object_literal() { @@ -62,6 +64,7 @@ fn check_object_short_function() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Ordinary, + PSEUDO_LINEAR_POS, )), ]; @@ -110,6 +113,7 @@ fn check_object_short_function_arguments() { parameters, FunctionBody::default(), MethodDefinitionKind::Ordinary, + PSEUDO_LINEAR_POS, )), ]; @@ -146,6 +150,7 @@ fn check_object_getter() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Get, + PSEUDO_LINEAR_POS, )), ]; @@ -193,6 +198,7 @@ fn check_object_setter() { params, FunctionBody::default(), MethodDefinitionKind::Set, + PSEUDO_LINEAR_POS, )), ]; @@ -225,6 +231,7 @@ fn check_object_short_function_get() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Ordinary, + PSEUDO_LINEAR_POS, ), )]; @@ -256,6 +263,7 @@ fn check_object_short_function_set() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Ordinary, + PSEUDO_LINEAR_POS, ), )]; @@ -404,6 +412,7 @@ fn check_async_method() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Async, + PSEUDO_LINEAR_POS, ), )]; @@ -435,6 +444,7 @@ fn check_async_generator_method() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::AsyncGenerator, + PSEUDO_LINEAR_POS, ), )]; @@ -488,6 +498,7 @@ fn check_async_ordinary_method() { FormalParameterList::default(), FunctionBody::default(), MethodDefinitionKind::Ordinary, + PSEUDO_LINEAR_POS, ), )]; diff --git a/core/parser/src/parser/expression/primary/template/mod.rs b/core/parser/src/parser/expression/primary/template/mod.rs index 10769febdd6..c1f5ff2980a 100644 --- a/core/parser/src/parser/expression/primary/template/mod.rs +++ b/core/parser/src/parser/expression/primary/template/mod.rs @@ -15,7 +15,7 @@ use crate::{ }; use boa_ast::{ expression::literal::{self, TemplateElement}, - Position, Punctuator, + PositionGroup, Punctuator, }; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; @@ -32,13 +32,18 @@ use boa_profiler::Profiler; pub(super) struct TemplateLiteral { allow_yield: AllowYield, allow_await: AllowAwait, - start: Position, + start: PositionGroup, first: Sym, } impl TemplateLiteral { /// Creates a new `TemplateLiteral` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, start: Position, first: Sym) -> Self + pub(super) fn new( + allow_yield: Y, + allow_await: A, + start: PositionGroup, + first: Sym, + ) -> Self where Y: Into, A: Into, diff --git a/core/parser/src/parser/expression/tests.rs b/core/parser/src/parser/expression/tests.rs index 37754d63ea0..8f4a2dddcf2 100644 --- a/core/parser/src/parser/expression/tests.rs +++ b/core/parser/src/parser/expression/tests.rs @@ -11,7 +11,7 @@ use boa_ast::{ Call, Identifier, Parenthesized, RegExpLiteral, }, function::{AsyncArrowFunction, FormalParameter, FormalParameterList, FunctionBody}, - Declaration, Expression, Statement, + Declaration, Expression, LinearPosition, LinearSpan, Statement, }; use boa_interner::{Interner, Sym}; use boa_macros::utf16; @@ -702,6 +702,7 @@ fn parse_async_arrow_function_named_of() { false, )]), FunctionBody::default(), + LinearSpan::new(LinearPosition::default(), LinearPosition::default()), ))) .into(), ], diff --git a/core/parser/src/parser/function/tests.rs b/core/parser/src/parser/function/tests.rs index 487a03bf453..554c859835c 100644 --- a/core/parser/src/parser/function/tests.rs +++ b/core/parser/src/parser/function/tests.rs @@ -15,6 +15,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + /// Checks basic function declaration parsing. #[test] fn check_basic() { @@ -37,8 +41,10 @@ fn check_basic() { Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -75,8 +81,10 @@ fn check_duplicates_strict_off() { Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -111,8 +119,10 @@ fn check_basic_semicolon_insertion() { Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -138,8 +148,10 @@ fn check_empty_return() { [StatementListItem::Statement(Statement::Return( Return::new(None), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -165,8 +177,10 @@ fn check_empty_return_semicolon_insertion() { [StatementListItem::Statement(Statement::Return( Return::new(None), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -198,6 +212,7 @@ fn check_rest_operator() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -223,6 +238,7 @@ fn check_arrow_only_rest() { None, params, FunctionBody::default(), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -258,6 +274,7 @@ fn check_arrow_rest() { None, params, FunctionBody::default(), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -296,8 +313,10 @@ fn check_arrow() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -334,8 +353,10 @@ fn check_arrow_semicolon_insertion() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -365,8 +386,10 @@ fn check_arrow_epty_return() { [StatementListItem::Statement(Statement::Return( Return::new(None), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -396,8 +419,10 @@ fn check_arrow_empty_return_semicolon_insertion() { [StatementListItem::Statement(Statement::Return( Return::new(None), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ))) .into()], interner, @@ -431,8 +456,10 @@ fn check_arrow_assignment() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -472,8 +499,10 @@ fn check_arrow_assignment_nobrackets() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -513,8 +542,10 @@ fn check_arrow_assignment_noparenthesis() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -554,8 +585,10 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -601,8 +634,10 @@ fn check_arrow_assignment_2arg() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -648,8 +683,10 @@ fn check_arrow_assignment_2arg_nobrackets() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -699,8 +736,10 @@ fn check_arrow_assignment_3arg() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), @@ -750,8 +789,10 @@ fn check_arrow_assignment_3arg_nobrackets() { .into(), )), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ) .into(), ), diff --git a/core/parser/src/parser/mod.rs b/core/parser/src/parser/mod.rs index 2dc352c860d..b24b0c111b3 100644 --- a/core/parser/src/parser/mod.rs +++ b/core/parser/src/parser/mod.rs @@ -35,6 +35,9 @@ use std::path::Path; use self::statement::ModuleItemList; +type ScriptParseOutput = (boa_ast::Script, boa_ast::SourceText); +type ModuleParseOutput = (boa_ast::Module, boa_ast::SourceText); + /// Trait implemented by parsers. /// /// This makes it possible to abstract over the underlying implementation of a parser. @@ -132,7 +135,7 @@ impl<'a, R: ReadChar> Parser<'a, R> { } } - /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation. + /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation without source text. /// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm. /// /// # Errors @@ -145,18 +148,34 @@ impl<'a, R: ReadChar> Parser<'a, R> { scope: &Scope, interner: &mut Interner, ) -> ParseResult { + self.parse_script_with_source(scope, interner).map(|x| x.0) + } + + /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation with source text. + /// The resulting `Script` can be compiled into boa bytecode and executed in the boa vm. + /// + /// # Errors + /// + /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. + /// + /// [spec]: https://tc39.es/ecma262/#prod-Script + pub fn parse_script_with_source( + &mut self, + scope: &Scope, + interner: &mut Interner, + ) -> ParseResult { self.cursor.set_goal(InputElement::HashbangOrRegExp); - let mut ast = ScriptParser::new(false).parse(&mut self.cursor, interner)?; + let (mut ast, source) = ScriptParser::new(false).parse(&mut self.cursor, interner)?; if !ast.analyze_scope(scope, interner) { return Err(Error::general( "invalid scope analysis", Position::new(1, 1), )); } - Ok(ast) + Ok((ast, source)) } - /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation. + /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation without source text. /// The resulting `ModuleItemList` can be compiled into boa bytecode and executed in the boa vm. /// /// # Errors @@ -169,18 +188,37 @@ impl<'a, R: ReadChar> Parser<'a, R> { scope: &Scope, interner: &mut Interner, ) -> ParseResult + where + R: ReadChar, + { + self.parse_module_with_source(scope, interner).map(|x| x.0) + } + + /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation with source text. + /// The resulting `ModuleItemList` can be compiled into boa bytecode and executed in the boa vm. + /// + /// # Errors + /// + /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. + /// + /// [spec]: https://tc39.es/ecma262/#prod-Module + pub fn parse_module_with_source( + &mut self, + scope: &Scope, + interner: &mut Interner, + ) -> ParseResult where R: ReadChar, { self.cursor.set_goal(InputElement::HashbangOrRegExp); - let mut module = ModuleParser.parse(&mut self.cursor, interner)?; + let (mut module, source) = ModuleParser.parse(&mut self.cursor, interner)?; if !module.analyze_scope(scope, interner) { return Err(Error::general( "invalid scope analysis", Position::new(1, 1), )); } - Ok(module) + Ok((module, source)) } /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] @@ -196,7 +234,7 @@ impl<'a, R: ReadChar> Parser<'a, R> { &mut self, direct: bool, interner: &mut Interner, - ) -> ParseResult { + ) -> ParseResult { self.cursor.set_goal(InputElement::HashbangOrRegExp); ScriptParser::new(direct).parse(&mut self.cursor, interner) } @@ -283,12 +321,12 @@ impl TokenParser for ScriptParser where R: ReadChar, { - type Output = boa_ast::Script; + type Output = ScriptParseOutput; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - let script = boa_ast::Script::new( - ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?, - ); + let stmts = + ScriptBody::new(true, cursor.strict(), self.direct_eval).parse(cursor, interner)?; + let script = boa_ast::Script::new(stmts); // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. let mut lexical_names = FxHashSet::default(); @@ -311,7 +349,8 @@ where } } - Ok(script) + let source = cursor.take_source(); + Ok((script, source)) } } @@ -416,7 +455,7 @@ impl TokenParser for ModuleParser where R: ReadChar, { - type Output = boa_ast::Module; + type Output = ModuleParseOutput; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { cursor.set_module(); @@ -515,7 +554,8 @@ where )); } - Ok(module) + let source = cursor.take_source(); + Ok((module, source)) } } diff --git a/core/parser/src/parser/statement/block/mod.rs b/core/parser/src/parser/statement/block/mod.rs index 85de83d855d..c8eca4809fc 100644 --- a/core/parser/src/parser/statement/block/mod.rs +++ b/core/parser/src/parser/statement/block/mod.rs @@ -81,7 +81,7 @@ where if let Some(tk) = cursor.peek(0, interner)? { if tk.kind() == &TokenKind::Punctuator(Punctuator::CloseBlock) { cursor.advance(interner); - return Ok(statement::Block::from(vec![])); + return Ok(statement::Block::from((vec![], cursor.linear_pos()))); } } let position = cursor.peek(0, interner).or_abrupt()?.span().start(); diff --git a/core/parser/src/parser/statement/block/tests.rs b/core/parser/src/parser/statement/block/tests.rs index 1d1e9170934..f58b3251b7d 100644 --- a/core/parser/src/parser/statement/block/tests.rs +++ b/core/parser/src/parser/statement/block/tests.rs @@ -19,6 +19,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + /// Helper function to check a block. #[track_caller] fn check_block(js: &str, block: B, interner: &mut Interner) @@ -27,7 +31,7 @@ where { check_script_parser( js, - vec![Statement::Block(Block::from(block.into())).into()], + vec![Statement::Block(Block::from((block.into(), PSEUDO_LINEAR_POS))).into()], interner, ); } @@ -85,8 +89,10 @@ fn non_empty() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(10).into())), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into(), Statement::Var(VarDeclaration( @@ -142,8 +148,10 @@ fn hoisting() { [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(10).into())), ))], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into(), ], diff --git a/core/parser/src/parser/statement/break_stm/tests.rs b/core/parser/src/parser/statement/break_stm/tests.rs index eef361e05ef..c3f2569bf93 100644 --- a/core/parser/src/parser/statement/break_stm/tests.rs +++ b/core/parser/src/parser/statement/break_stm/tests.rs @@ -7,6 +7,16 @@ use boa_ast::{ use boa_interner::{Interner, Sym}; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + +fn stmt_block_break_only(break_stmt: Break) -> Statement { + Block::from(( + vec![StatementListItem::Statement(Statement::Break(break_stmt))], + PSEUDO_LINEAR_POS, + )) + .into() +} + #[test] fn inline() { check_script_parser( @@ -40,10 +50,7 @@ fn inline_block_semicolon_insertion() { "while (true) {break}", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(None), - ))]) - .into(), + stmt_block_break_only(Break::new(None)), )) .into()], &mut Interner::default(), @@ -60,10 +67,9 @@ fn new_line_semicolon_insertion() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some(interner.get_or_intern_static("test", utf16!("test")))), - ))]) - .into(), + stmt_block_break_only(Break::new(Some( + interner.get_or_intern_static("test", utf16!("test")), + ))), ))), interner.get_or_intern_static("test", utf16!("test")), )) @@ -78,10 +84,7 @@ fn inline_block() { "while (true) {break;}", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(None), - ))]) - .into(), + stmt_block_break_only(Break::new(None)), )) .into()], &mut Interner::default(), @@ -98,10 +101,9 @@ fn new_line_block() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some(interner.get_or_intern_static("test", utf16!("test")))), - ))]) - .into(), + stmt_block_break_only(Break::new(Some( + interner.get_or_intern_static("test", utf16!("test")), + ))), ))), interner.get_or_intern_static("test", utf16!("test")), )) @@ -120,10 +122,7 @@ fn reserved_label() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some(Sym::AWAIT)), - ))]) - .into(), + stmt_block_break_only(Break::new(Some(Sym::AWAIT))), ))), Sym::AWAIT, )) @@ -139,10 +138,7 @@ fn reserved_label() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(Some(Sym::YIELD)), - ))]) - .into(), + stmt_block_break_only(Break::new(Some(Sym::YIELD))), ))), Sym::YIELD, )) @@ -159,10 +155,7 @@ fn new_line_block_empty() { }", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(None), - ))]) - .into(), + stmt_block_break_only(Break::new(None)), )) .into()], &mut Interner::default(), @@ -177,10 +170,7 @@ fn new_line_block_empty_semicolon_insertion() { }", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(None), - ))]) - .into(), + stmt_block_break_only(Break::new(None)), )) .into()], &mut Interner::default(), diff --git a/core/parser/src/parser/statement/continue_stm/tests.rs b/core/parser/src/parser/statement/continue_stm/tests.rs index 9891b7c2c76..d49de6c3dc5 100644 --- a/core/parser/src/parser/statement/continue_stm/tests.rs +++ b/core/parser/src/parser/statement/continue_stm/tests.rs @@ -7,6 +7,18 @@ use boa_ast::{ use boa_interner::{Interner, Sym}; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + +fn stmt_block_continue_only(continue_stmt: Continue) -> Statement { + Block::from(( + vec![StatementListItem::Statement(Statement::Continue( + continue_stmt, + ))], + PSEUDO_LINEAR_POS, + )) + .into() +} + #[test] fn inline() { check_script_parser( @@ -40,10 +52,7 @@ fn inline_block_semicolon_insertion() { "while (true) {continue}", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(None), - ))]) - .into(), + stmt_block_continue_only(Continue::new(None)), )) .into()], &mut Interner::default(), @@ -60,10 +69,9 @@ fn new_line_semicolon_insertion() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some(interner.get_or_intern_static("test", utf16!("test")))), - ))]) - .into(), + stmt_block_continue_only(Continue::new(Some( + interner.get_or_intern_static("test", utf16!("test")), + ))), ))), interner.get_or_intern_static("test", utf16!("test")), )) @@ -78,10 +86,7 @@ fn inline_block() { "while (true) {continue;}", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(None), - ))]) - .into(), + stmt_block_continue_only(Continue::new(None)), )) .into()], &mut Interner::default(), @@ -98,10 +103,9 @@ fn new_line_block() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some(interner.get_or_intern_static("test", utf16!("test")))), - ))]) - .into(), + stmt_block_continue_only(Continue::new(Some( + interner.get_or_intern_static("test", utf16!("test")), + ))), ))), interner.get_or_intern_static("test", utf16!("test")), )) @@ -120,10 +124,7 @@ fn reserved_label() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some(Sym::AWAIT)), - ))]) - .into(), + stmt_block_continue_only(Continue::new(Some(Sym::AWAIT))), ))), Sym::AWAIT, )) @@ -139,10 +140,7 @@ fn reserved_label() { vec![Statement::Labelled(Labelled::new( LabelledItem::Statement(Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(Some(Sym::YIELD)), - ))]) - .into(), + stmt_block_continue_only(Continue::new(Some(Sym::YIELD))), ))), Sym::YIELD, )) @@ -159,10 +157,7 @@ fn new_line_block_empty() { }", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(None), - ))]) - .into(), + stmt_block_continue_only(Continue::new(None)), )) .into()], &mut Interner::default(), @@ -177,10 +172,7 @@ fn new_line_block_empty_semicolon_insertion() { }", vec![Statement::WhileLoop(WhileLoop::new( Literal::from(true).into(), - Block::from(vec![StatementListItem::Statement(Statement::Continue( - Continue::new(None), - ))]) - .into(), + stmt_block_continue_only(Continue::new(None)), )) .into()], &mut Interner::default(), diff --git a/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs index 97a01f57898..08a2bb2efdb 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/mod.rs @@ -79,11 +79,13 @@ where type Output = AsyncFunctionDeclarationNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - cursor.expect( + let async_token = cursor.expect( (Keyword::Async, false), "async function declaration", interner, )?; + let start_linear_span = async_token.linear_span(); + cursor.peek_expect_no_lineterminator(0, "async function declaration", interner)?; cursor.expect( (Keyword::Function, false), @@ -92,9 +94,10 @@ where )?; let result = parse_callable_declaration(&self, cursor, interner)?; + let span = start_linear_span.union(result.2.linear_pos_end()); Ok(AsyncFunctionDeclarationNode::new( - result.0, result.1, result.2, + result.0, result.1, result.2, span, )) } } diff --git a/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs index e42798f412f..9dac53c4c57 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/async_function_decl/tests.rs @@ -1,11 +1,14 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ function::{AsyncFunctionDeclaration, FormalParameterList, FunctionBody}, - Declaration, + Declaration, LinearPosition, LinearSpan, }; use boa_interner::{Interner, Sym}; use boa_macros::utf16; +const EMPTY_LINEAR_SPAN: LinearSpan = + LinearSpan::new(LinearPosition::new(0), LinearPosition::new(0)); + /// Async function declaration parsing. #[test] fn async_function_declaration() { @@ -19,6 +22,7 @@ fn async_function_declaration() { .into(), FormalParameterList::default(), FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into(), ], @@ -37,6 +41,7 @@ fn async_function_declaration_keywords() { Sym::YIELD.into(), FormalParameterList::default(), FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into(), ], @@ -51,6 +56,7 @@ fn async_function_declaration_keywords() { Sym::AWAIT.into(), FormalParameterList::default(), FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into(), ], diff --git a/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs index 4438041f096..a972a437f74 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/mod.rs @@ -94,11 +94,13 @@ where type Output = AsyncGeneratorDeclarationNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - cursor.expect( + let async_token = cursor.expect( (Keyword::Async, false), "async generator declaration", interner, )?; + let start_linear_span = async_token.linear_span(); + cursor.peek_expect_no_lineterminator(0, "async generator declaration", interner)?; cursor.expect( (Keyword::Function, false), @@ -108,9 +110,10 @@ where cursor.expect(Punctuator::Mul, "async generator declaration", interner)?; let result = parse_callable_declaration(&self, cursor, interner)?; + let span = start_linear_span.union(result.2.linear_pos_end()); Ok(AsyncGeneratorDeclarationNode::new( - result.0, result.1, result.2, + result.0, result.1, result.2, span, )) } } diff --git a/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs index 2f19eb339a8..c1754a1305c 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/async_generator_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ function::{AsyncGeneratorDeclaration, FormalParameterList, FunctionBody}, - Declaration, + Declaration, LinearPosition, LinearSpan, }; use boa_interner::Interner; use boa_macros::utf16; @@ -16,6 +16,7 @@ fn async_generator_function_declaration() { interner.get_or_intern_static("gen", utf16!("gen")).into(), FormalParameterList::default(), FunctionBody::default(), + LinearSpan::new(LinearPosition::default(), LinearPosition::default()), )) .into(), ], diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index cc992dd786a..a0e26fc04c4 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -568,6 +568,9 @@ where ); let token = cursor.peek(0, interner).or_abrupt()?; + let start_linear_span = token.linear_span(); + let start_linear_pos = start_linear_span.start(); + let position = token.span().start(); let element = match token.kind() { TokenKind::IdentifierName((Sym::CONSTRUCTOR, _)) if !r#static => { @@ -590,8 +593,12 @@ where )?; cursor.set_strict(strict); + let span = Some(start_linear_span.union(body.linear_pos_end())); + return Ok(( - Some(FunctionExpression::new(self.name, parameters, body, false)), + Some(FunctionExpression::new( + self.name, parameters, body, span, false, + )), None, )); } @@ -724,6 +731,7 @@ where body, MethodDefinitionKind::Generator, r#static, + start_linear_pos, )) } TokenKind::Keyword((Keyword::Async, true)) if is_keyword => { @@ -783,6 +791,7 @@ where body, MethodDefinitionKind::AsyncGenerator, r#static, + start_linear_pos, )) } TokenKind::IdentifierName((Sym::CONSTRUCTOR, _)) if !r#static => { @@ -826,6 +835,7 @@ where body, MethodDefinitionKind::Async, r#static, + start_linear_pos, )) } } @@ -883,6 +893,7 @@ where body, MethodDefinitionKind::Get, r#static, + start_linear_pos, )) } TokenKind::IdentifierName((Sym::CONSTRUCTOR, _)) if !r#static => { @@ -936,6 +947,7 @@ where body, MethodDefinitionKind::Get, r#static, + start_linear_pos, )) } _ => { @@ -997,6 +1009,7 @@ where body, MethodDefinitionKind::Set, r#static, + start_linear_pos, )) } TokenKind::IdentifierName((Sym::CONSTRUCTOR, _)) if !r#static => { @@ -1052,6 +1065,7 @@ where body, MethodDefinitionKind::Set, r#static, + start_linear_pos, )) } _ => { @@ -1133,6 +1147,7 @@ where body, MethodDefinitionKind::Ordinary, r#static, + start_linear_pos, )) } _ => { @@ -1230,6 +1245,7 @@ where body, MethodDefinitionKind::Ordinary, r#static, + start_linear_pos, )) } _ => { diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs index 95305bd81e2..898f931c91d 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -28,6 +28,7 @@ fn check_async_ordinary_method() { FunctionBody::default(), MethodDefinitionKind::Ordinary, false, + boa_ast::LinearPosition::default(), ))]; check_script_parser( @@ -121,7 +122,12 @@ fn check_new_target_with_property_access() { let constructor = FunctionExpression::new( Some(interner.get_or_intern_static("A", utf16!("A")).into()), FormalParameterList::default(), - FunctionBody::new([Statement::Expression(console).into()], false), + FunctionBody::new( + [Statement::Expression(console).into()], + boa_ast::LinearPosition::new(0), + false, + ), + None, false, ); diff --git a/core/parser/src/parser/statement/declaration/hoistable/function_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/function_decl/mod.rs index da764fdd9fa..028cd5f6c12 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/function_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/function_decl/mod.rs @@ -77,10 +77,16 @@ where type Output = FunctionDeclarationNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - cursor.expect((Keyword::Function, false), "function declaration", interner)?; + let func_token = + cursor.expect((Keyword::Function, false), "function declaration", interner)?; + let func_token_span = func_token.linear_span(); let result = parse_callable_declaration(&self, cursor, interner)?; + let linear_pos_end = result.2.linear_pos_end(); + let span = func_token_span.union(linear_pos_end); - Ok(FunctionDeclarationNode::new(result.0, result.1, result.2)) + Ok(FunctionDeclarationNode::new( + result.0, result.1, result.2, span, + )) } } diff --git a/core/parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs index 274c97b2f59..6c7db614068 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/function_decl/tests.rs @@ -6,6 +6,10 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: boa_ast::LinearSpan = + boa_ast::LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + /// Function declaration parsing. #[test] fn function_declaration() { @@ -18,6 +22,7 @@ fn function_declaration() { .into(), FormalParameterList::default(), FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into()], interner, @@ -35,6 +40,7 @@ fn function_declaration_keywords() { .into(), FormalParameterList::default(), FunctionBody::default(), + EMPTY_LINEAR_SPAN, )) .into()] }; diff --git a/core/parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs index 30681a03822..dcd61aa0c8f 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/generator_decl/mod.rs @@ -79,15 +79,20 @@ where type Output = GeneratorDeclarationNode; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - cursor.expect( + let func_token = cursor.expect( (Keyword::Function, false), "generator declaration", interner, )?; + let start_linear_span = func_token.linear_span(); + cursor.expect(Punctuator::Mul, "generator declaration", interner)?; let result = parse_callable_declaration(&self, cursor, interner)?; + let span = start_linear_span.union(result.2.linear_pos_end()); - Ok(GeneratorDeclarationNode::new(result.0, result.1, result.2)) + Ok(GeneratorDeclarationNode::new( + result.0, result.1, result.2, span, + )) } } diff --git a/core/parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs index b8e01341618..74390fc0458 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/generator_decl/tests.rs @@ -1,7 +1,7 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ function::{FormalParameterList, FunctionBody, GeneratorDeclaration}, - Declaration, + Declaration, LinearPosition, LinearSpan, }; use boa_interner::Interner; use boa_macros::utf16; @@ -15,6 +15,7 @@ fn generator_function_declaration() { interner.get_or_intern_static("gen", utf16!("gen")).into(), FormalParameterList::default(), FunctionBody::default(), + LinearSpan::new(LinearPosition::default(), LinearPosition::default()), )) .into()], interner, diff --git a/core/parser/src/parser/statement/if_stm/mod.rs b/core/parser/src/parser/statement/if_stm/mod.rs index ec083afd510..310251477e8 100644 --- a/core/parser/src/parser/statement/if_stm/mod.rs +++ b/core/parser/src/parser/statement/if_stm/mod.rs @@ -83,12 +83,15 @@ where // Source text matched by this production is processed as if each matching // occurrence of FunctionDeclaration[?Yield, ?Await, ~Default] was the sole // StatementListItem of a BlockStatement occupying that position in the source text. - Block::from(vec![StatementListItem::Declaration( - Declaration::FunctionDeclaration( - FunctionDeclaration::new(self.allow_yield, self.allow_await, false) - .parse(cursor, interner)?, - ), - )]) + Block::from(( + vec![StatementListItem::Declaration( + Declaration::FunctionDeclaration( + FunctionDeclaration::new(self.allow_yield, self.allow_await, false) + .parse(cursor, interner)?, + ), + )], + cursor.linear_pos(), + )) .into() } _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) @@ -127,16 +130,19 @@ where // Source text matched by this production is processed as if each matching // occurrence of FunctionDeclaration[?Yield, ?Await, ~Default] was the sole // StatementListItem of a BlockStatement occupying that position in the source text. - Block::from(vec![StatementListItem::Declaration( - Declaration::FunctionDeclaration( - FunctionDeclaration::new( - self.allow_yield, - self.allow_await, - false, - ) - .parse(cursor, interner)?, - ), - )]) + Block::from(( + vec![StatementListItem::Declaration( + Declaration::FunctionDeclaration( + FunctionDeclaration::new( + self.allow_yield, + self.allow_await, + false, + ) + .parse(cursor, interner)?, + ), + )], + cursor.linear_pos(), + )) .into() } _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) diff --git a/core/parser/src/parser/statement/if_stm/tests.rs b/core/parser/src/parser/statement/if_stm/tests.rs index 4a22407f278..0a498896f10 100644 --- a/core/parser/src/parser/statement/if_stm/tests.rs +++ b/core/parser/src/parser/statement/if_stm/tests.rs @@ -6,13 +6,15 @@ use boa_ast::{ }; use boa_interner::Interner; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + #[test] fn if_without_else_block() { check_script_parser( "if (true) {}", vec![Statement::If(If::new( Literal::from(true).into(), - Block::from(Vec::new()).into(), + Block::from((Vec::new(), PSEUDO_LINEAR_POS)).into(), None, )) .into()], @@ -26,7 +28,7 @@ fn if_without_else_block_with_trailing_newline() { "if (true) {}\n", vec![Statement::If(If::new( Literal::from(true).into(), - Block::from(Vec::new()).into(), + Block::from((Vec::new(), PSEUDO_LINEAR_POS)).into(), None, )) .into()], diff --git a/core/parser/src/parser/statement/iteration/tests.rs b/core/parser/src/parser/statement/iteration/tests.rs index 314914a1183..1e10f82b859 100644 --- a/core/parser/src/parser/statement/iteration/tests.rs +++ b/core/parser/src/parser/statement/iteration/tests.rs @@ -18,6 +18,8 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + /// Checks do-while statement parsing. #[test] fn check_do_while() { @@ -28,14 +30,17 @@ fn check_do_while() { } while (true)"#, vec![Statement::DoWhileLoop(DoWhileLoop::new( Statement::Block( - vec![StatementListItem::Statement(Statement::Expression( - Expression::from(Assign::new( - AssignOp::Add, - Identifier::new(interner.get_or_intern_static("a", utf16!("a"))).into(), - Literal::from(1).into(), - )), - ))] - .into(), + ( + vec![StatementListItem::Statement(Statement::Expression( + Expression::from(Assign::new( + AssignOp::Add, + Identifier::new(interner.get_or_intern_static("a", utf16!("a"))).into(), + Literal::from(1).into(), + )), + ))], + PSEUDO_LINEAR_POS, + ) + .into(), ), Literal::from(true).into(), )) @@ -63,26 +68,30 @@ fn check_do_while_semicolon_insertion() { .into(), Statement::DoWhileLoop(DoWhileLoop::new( Statement::Block( - vec![StatementListItem::Statement(Statement::Expression( - Expression::from(Call::new( - Expression::PropertyAccess( - SimplePropertyAccess::new( - Identifier::new( - interner.get_or_intern_static("console", utf16!("console")), + ( + vec![StatementListItem::Statement(Statement::Expression( + Expression::from(Call::new( + Expression::PropertyAccess( + SimplePropertyAccess::new( + Identifier::new( + interner + .get_or_intern_static("console", utf16!("console")), + ) + .into(), + interner.get_or_intern_static("log", utf16!("log")), ) .into(), - interner.get_or_intern_static("log", utf16!("log")), + ), + vec![Literal::from( + interner.get_or_intern_static("hello", utf16!("hello")), ) + .into()] .into(), - ), - vec![Literal::from( - interner.get_or_intern_static("hello", utf16!("hello")), - ) - .into()] - .into(), - )), - ))] - .into(), + )), + ))], + PSEUDO_LINEAR_POS, + ) + .into(), ), Binary::new( RelationalOp::LessThan.into(), @@ -138,26 +147,30 @@ fn check_do_while_semicolon_insertion_no_space() { .into(), Statement::DoWhileLoop(DoWhileLoop::new( Statement::Block( - vec![StatementListItem::Statement(Statement::Expression( - Expression::from(Call::new( - Expression::PropertyAccess( - SimplePropertyAccess::new( - Identifier::new( - interner.get_or_intern_static("console", utf16!("console")), + ( + vec![StatementListItem::Statement(Statement::Expression( + Expression::from(Call::new( + Expression::PropertyAccess( + SimplePropertyAccess::new( + Identifier::new( + interner + .get_or_intern_static("console", utf16!("console")), + ) + .into(), + interner.get_or_intern_static("log", utf16!("log")), ) .into(), - interner.get_or_intern_static("log", utf16!("log")), + ), + vec![Literal::from( + interner.get_or_intern_static("hello", utf16!("hello")), ) + .into()] .into(), - ), - vec![Literal::from( - interner.get_or_intern_static("hello", utf16!("hello")), - ) - .into()] - .into(), - )), - ))] - .into(), + )), + ))], + PSEUDO_LINEAR_POS, + ) + .into(), ), Binary::new( RelationalOp::LessThan.into(), @@ -237,9 +250,12 @@ fn do_while_spaces() { "#, vec![Statement::DoWhileLoop(DoWhileLoop::new( - Block::from(vec![StatementListItem::Statement(Statement::Break( - Break::new(None), - ))]) + Block::from(( + vec![StatementListItem::Statement(Statement::Break(Break::new( + None, + )))], + PSEUDO_LINEAR_POS, + )) .into(), Literal::Bool(true).into(), )) diff --git a/core/parser/src/parser/statement/mod.rs b/core/parser/src/parser/statement/mod.rs index c19e9ec0dfd..a04b97e866a 100644 --- a/core/parser/src/parser/statement/mod.rs +++ b/core/parser/src/parser/statement/mod.rs @@ -295,9 +295,15 @@ where let mut directive_prologues = self.directive_prologues; let mut strict = self.strict; let mut directives_stack = Vec::new(); + let mut linear_pos_end = cursor.linear_pos(); loop { - match cursor.peek(0, interner)? { + let peek_token = cursor.peek(0, interner)?; + if let Some(peek_token) = peek_token { + linear_pos_end = peek_token.linear_span().end(); + } + + match peek_token { Some(token) if self.break_nodes.contains(token.kind()) => break, Some(token) if directive_prologues => { if let TokenKind::StringLiteral((_, escape)) = token.kind() { @@ -365,7 +371,7 @@ where cursor.set_strict(global_strict); - Ok(ast::StatementList::new(items, strict)) + Ok(ast::StatementList::new(items, linear_pos_end, strict)) } } diff --git a/core/parser/src/parser/statement/switch/tests.rs b/core/parser/src/parser/statement/switch/tests.rs index b1de2ff8392..29d196ebb33 100644 --- a/core/parser/src/parser/statement/switch/tests.rs +++ b/core/parser/src/parser/statement/switch/tests.rs @@ -8,6 +8,8 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + /// Checks parsing malformed switch with no closeblock. #[test] fn check_switch_no_closeblock() { @@ -169,48 +171,63 @@ fn check_separated_switch() { vec![ Case::new( Literal::from(5).into(), - vec![ - Statement::Expression(Expression::from(Call::new( - Expression::PropertyAccess( - SimplePropertyAccess::new(Identifier::new(console).into(), log) + ( + vec![ + Statement::Expression(Expression::from(Call::new( + Expression::PropertyAccess( + SimplePropertyAccess::new( + Identifier::new(console).into(), + log, + ) .into(), - ), - vec![Literal::from(5).into()].into(), - ))) + ), + vec![Literal::from(5).into()].into(), + ))) + .into(), + Statement::Break(Break::new(None)).into(), + ], + PSEUDO_LINEAR_POS, + ) .into(), - Statement::Break(Break::new(None)).into(), - ] - .into(), ), Case::new( Literal::from(10).into(), - vec![ - Statement::Expression(Expression::from(Call::new( + ( + vec![ + Statement::Expression(Expression::from(Call::new( + Expression::PropertyAccess( + SimplePropertyAccess::new( + Identifier::new(console).into(), + log, + ) + .into(), + ), + vec![Literal::from(10).into()].into(), + ))) + .into(), + Statement::Break(Break::new(None)).into(), + ], + PSEUDO_LINEAR_POS, + ) + .into(), + ), + Case::default( + ( + vec![Statement::Expression(Expression::from(Call::new( Expression::PropertyAccess( SimplePropertyAccess::new(Identifier::new(console).into(), log) .into(), ), - vec![Literal::from(10).into()].into(), + vec![Literal::from( + interner.get_or_intern_static("Default", utf16!("Default")), + ) + .into()] + .into(), ))) + .into()], + PSEUDO_LINEAR_POS, + ) .into(), - Statement::Break(Break::new(None)).into(), - ] - .into(), - ), - Case::default( - vec![Statement::Expression(Expression::from(Call::new( - Expression::PropertyAccess( - SimplePropertyAccess::new(Identifier::new(console).into(), log) - .into(), - ), - vec![Literal::from( - interner.get_or_intern_static("Default", utf16!("Default")), - ) - .into()] - .into(), - ))) - .into()] - .into(), ), ] .into(), diff --git a/core/parser/src/parser/statement/try_stm/tests.rs b/core/parser/src/parser/statement/try_stm/tests.rs index 2320deff47e..9d052e59097 100644 --- a/core/parser/src/parser/statement/try_stm/tests.rs +++ b/core/parser/src/parser/statement/try_stm/tests.rs @@ -10,6 +10,8 @@ use boa_ast::{ use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: boa_ast::LinearPosition = boa_ast::LinearPosition::new(0); + #[test] fn check_inline_with_empty_try_catch() { let interner = &mut Interner::default(); @@ -33,16 +35,19 @@ fn check_inline_with_var_decl_inside_try() { check_script_parser( "try { var x = 1; } catch(e) {}", vec![Statement::Try(Try::new( - vec![Statement::Var(VarDeclaration( - vec![Variable::from_identifier( - interner.get_or_intern_static("x", utf16!("x")).into(), - Some(Literal::from(1).into()), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ( + vec![Statement::Var(VarDeclaration( + vec![Variable::from_identifier( + interner.get_or_intern_static("x", utf16!("x")).into(), + Some(Literal::from(1).into()), + )] + .try_into() + .unwrap(), + )) + .into()], + PSEUDO_LINEAR_POS, + ) + .into(), ErrorHandler::Catch(Catch::new( Some(Identifier::from(interner.get_or_intern_static("e", utf16!("e"))).into()), Block::default(), @@ -59,18 +64,7 @@ fn check_inline_with_var_decl_inside_catch() { check_script_parser( "try { var x = 1; } catch(e) { var x = 1; }", vec![Statement::Try(Try::new( - vec![Statement::Var(VarDeclaration( - vec![Variable::from_identifier( - interner.get_or_intern_static("x", utf16!("x")).into(), - Some(Literal::from(1).into()), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), - ErrorHandler::Catch(Catch::new( - Some(Identifier::from(interner.get_or_intern_static("e", utf16!("e"))).into()), + ( vec![Statement::Var(VarDeclaration( vec![Variable::from_identifier( interner.get_or_intern_static("x", utf16!("x")).into(), @@ -79,8 +73,25 @@ fn check_inline_with_var_decl_inside_catch() { .try_into() .unwrap(), )) - .into()] + .into()], + PSEUDO_LINEAR_POS, + ) .into(), + ErrorHandler::Catch(Catch::new( + Some(Identifier::from(interner.get_or_intern_static("e", utf16!("e"))).into()), + ( + vec![Statement::Var(VarDeclaration( + vec![Variable::from_identifier( + interner.get_or_intern_static("x", utf16!("x")).into(), + Some(Literal::from(1).into()), + )] + .try_into() + .unwrap(), + )) + .into()], + PSEUDO_LINEAR_POS, + ) + .into(), )), )) .into()], @@ -128,16 +139,19 @@ fn check_inline_with_empty_try_var_decl_in_finally() { "try {} finally { var x = 1; }", vec![Statement::Try(Try::new( Block::default(), - ErrorHandler::Finally(Finally::from(Block::from(vec![ - StatementListItem::Statement(Statement::Var(VarDeclaration( - vec![Variable::from_identifier( - interner.get_or_intern_static("x", utf16!("x")).into(), - Some(Literal::from(1).into()), - )] - .try_into() - .unwrap(), - ))), - ]))), + ErrorHandler::Finally(Finally::from(Block::from(( + vec![StatementListItem::Statement(Statement::Var( + VarDeclaration( + vec![Variable::from_identifier( + interner.get_or_intern_static("x", utf16!("x")).into(), + Some(Literal::from(1).into()), + )] + .try_into() + .unwrap(), + ), + ))], + PSEUDO_LINEAR_POS, + )))), )) .into()], interner, @@ -153,16 +167,19 @@ fn check_inline_empty_try_paramless_catch() { Block::default(), ErrorHandler::Catch(Catch::new( None, - vec![Statement::Var(VarDeclaration( - vec![Variable::from_identifier( - interner.get_or_intern_static("x", utf16!("x")).into(), - Some(Literal::from(1).into()), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ( + vec![Statement::Var(VarDeclaration( + vec![Variable::from_identifier( + interner.get_or_intern_static("x", utf16!("x")).into(), + Some(Literal::from(1).into()), + )] + .try_into() + .unwrap(), + )) + .into()], + PSEUDO_LINEAR_POS, + ) + .into(), )), )) .into()], @@ -210,7 +227,7 @@ fn check_inline_with_binding_pattern_array() { check_script_parser( "try {} catch ([a, b]) {}", vec![Statement::Try(Try::new( - Block::from(vec![]), + Block::from((vec![], PSEUDO_LINEAR_POS)), ErrorHandler::Catch(Catch::new( Some( Pattern::from(vec![ @@ -239,21 +256,25 @@ fn check_catch_with_var_redeclaration() { check_script_parser( "try {} catch(e) { var e = 'oh' }", vec![Statement::Try(Try::new( - Block::from(vec![]), + Block::from((vec![], PSEUDO_LINEAR_POS)), ErrorHandler::Catch(Catch::new( Some(Identifier::new(interner.get_or_intern_static("e", utf16!("e"))).into()), - vec![Statement::Var(VarDeclaration( - vec![Variable::from_identifier( - interner.get_or_intern_static("e", utf16!("e")).into(), - Some( - Literal::from(interner.get_or_intern_static("oh", utf16!("oh"))).into(), - ), - )] - .try_into() - .unwrap(), - )) - .into()] - .into(), + ( + vec![Statement::Var(VarDeclaration( + vec![Variable::from_identifier( + interner.get_or_intern_static("e", utf16!("e")).into(), + Some( + Literal::from(interner.get_or_intern_static("oh", utf16!("oh"))) + .into(), + ), + )] + .try_into() + .unwrap(), + )) + .into()], + PSEUDO_LINEAR_POS, + ) + .into(), )), )) .into()], diff --git a/core/parser/src/parser/tests/mod.rs b/core/parser/src/parser/tests/mod.rs index c32312520da..cb27b8e02b7 100644 --- a/core/parser/src/parser/tests/mod.rs +++ b/core/parser/src/parser/tests/mod.rs @@ -24,19 +24,22 @@ use boa_ast::{ }, scope::Scope, statement::{If, Return}, - Expression, Module, ModuleItem, ModuleItemList, Script, Statement, StatementList, - StatementListItem, + Expression, LinearPosition, LinearSpan, Module, ModuleItem, ModuleItemList, Script, Statement, + StatementList, StatementListItem, }; use boa_interner::Interner; use boa_macros::utf16; +const PSEUDO_LINEAR_POS: LinearPosition = LinearPosition::new(0); +const EMPTY_LINEAR_SPAN: LinearSpan = LinearSpan::new(PSEUDO_LINEAR_POS, PSEUDO_LINEAR_POS); + /// Checks that the given JavaScript string gives the expected expression. #[track_caller] pub(super) fn check_script_parser(js: &str, expr: L, interner: &mut Interner) where L: Into>, { - let mut script = Script::new(StatementList::from(expr.into())); + let mut script = Script::new(StatementList::from((expr.into(), PSEUDO_LINEAR_POS))); let scope = Scope::new_global(); script.analyze_scope(&scope, interner); assert_eq!( @@ -149,8 +152,10 @@ fn hoisting() { FormalParameterList::default(), FunctionBody::new( [Statement::Return(Return::new(Some(Literal::from(10).into()))).into()], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, )) .into(), ], @@ -531,8 +536,10 @@ fn spread_in_arrow_function() { params, FunctionBody::new( [Statement::Expression(Expression::from(Identifier::from(b))).into()], + PSEUDO_LINEAR_POS, false, ), + EMPTY_LINEAR_SPAN, ))) .into()], interner,