Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,7 @@ impl Data {
properties,
has_decorators: el.has_decorators,
should_lower_standard_decorators: el.should_lower_standard_decorators,
ts_decorators_use_private_names: el.ts_decorators_use_private_names,
});
Ok(Data::EClass(StoreRef::from_bump(item)))
}
Expand Down
3 changes: 3 additions & 0 deletions src/ast/g.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub struct Class {
pub properties: StoreSlice<Property>,
pub has_decorators: bool,
pub should_lower_standard_decorators: bool,
/// A member or parameter decorator reads a `#private` name of this class.
pub ts_decorators_use_private_names: bool,
}

impl Default for Class {
Expand All @@ -77,6 +79,7 @@ impl Default for Class {
properties: StoreSlice::EMPTY,
has_decorators: false,
should_lower_standard_decorators: false,
ts_decorators_use_private_names: false,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/ast/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,8 @@ pub mod flags {
IsStatic,
WasShorthand,
IsSpread,
/// The getter a TypeScript `accessor` member was lowered to (keeps its decorators).
IsAutoAccessorGetter,
}
pub type PropertySet = EnumSet<Property>;
pub const PROPERTY_NONE: PropertySet = EnumSet::empty();
Expand Down
9 changes: 7 additions & 2 deletions src/js_parser/lower/lower_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ fn class_copy(c: &G::Class) -> G::Class {
properties: c.properties,
has_decorators: c.has_decorators,
should_lower_standard_decorators: c.should_lower_standard_decorators,
ts_decorators_use_private_names: c.ts_decorators_use_private_names,
}
}

Expand Down Expand Up @@ -273,7 +274,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}

/// Create a static block property from a single expression.
fn make_static_block(&mut self, expr: Expr, l: bun_ast::Loc) -> Property {
pub(crate) fn make_static_block(&mut self, expr: Expr, l: bun_ast::Loc) -> Property {
let bump = self.arena;
let stmt = self.s(
S::SExpr {
Expand Down Expand Up @@ -890,7 +891,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

/// Drain receiver-capture temporaries created past `baseline` into a
/// single `var` declaration statement; `None` if none were created.
fn drain_capture_temp_decls(&mut self, baseline: usize, loc: bun_ast::Loc) -> Option<Stmt> {
pub(crate) fn drain_capture_temp_decls(
&mut self,
baseline: usize,
loc: bun_ast::Loc,
) -> Option<Stmt> {
let total = self.temp_refs_to_declare.len();
if total == baseline {
return None;
Expand Down
560 changes: 354 additions & 206 deletions src/js_parser/p.rs

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/js_parser/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
allow_ts_decorators: class_opts.allow_ts_decorators,
class_has_extends: extends.is_some(),
has_argument_decorators: false,
decorator_scope: Some(p.current_scope),
..Default::default()
};

// Parse decorators for this property
let first_decorator_loc = p.lexer.loc();
let first_decorator_range = p.lexer.range();
let property_scope_index = p.scopes_in_order.len();
if opts.allow_ts_decorators {
opts.ts_decorators = p.parse_type_script_decorators()?;
Expand All @@ -221,6 +223,26 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// read fields before move (G::Property is not Copy).
let prop_kind = property.kind;
let prop_key = property.key;

// tsc and esbuild reject them too: the lowering needs a statement.
if Self::IS_TYPESCRIPT_ENABLED
&& !p.options.features.standard_decorators
&& class_opts.is_class_expr
{
let decorator_range = if opts.ts_decorators.len() > 0 {
Some(first_decorator_range)
} else {
Self::first_argument_decorator_range(&property)
};
if let Some(range) = decorator_range {
p.log().add_range_error(
Some(p.source),
range,
b"TypeScript experimental decorators can only be used with class declarations",
);
}
}

properties.push(property);
has_auto_accessor =
has_auto_accessor || prop_kind == js_ast::g::PropertyKind::AutoAccessor;
Expand Down Expand Up @@ -275,9 +297,27 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
has_decorators: has_any_decorators,
should_lower_standard_decorators: p.options.features.standard_decorators
&& (has_any_decorators || has_auto_accessor),
ts_decorators_use_private_names: false,
})
}

/// The location of the first parameter decorator of a method property, if any.
fn first_argument_decorator_range(property: &G::Property) -> Option<bun_ast::Range> {
let value = property.value?;
let js_ast::expr::Data::EFunction(func) = value.data else {
return None;
};
func.func
.args
.iter()
.flat_map(|arg| arg.ts_decorators.slice().iter())
.next()
.map(|decorator| bun_ast::Range {
loc: decorator.loc,
len: 0,
})
}

pub(crate) fn parse_template_parts(
&mut self,
include_raw: bool,
Expand Down
17 changes: 16 additions & 1 deletion src/js_parser/parse/parse_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,22 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

let mut ts_decorators = bun_alloc::AstAlloc::vec();
if opts.allow_ts_decorators {
ts_decorators = p.parse_type_script_decorators()?;
// Parameter decorators are evaluated outside the method, with the class.
let inner_fn_or_arrow_data = p.fn_or_arrow_data_parse.clone();
let inner_scope = p.current_scope;
p.fn_or_arrow_data_parse.allow_await = old_fn_or_arrow_data.allow_await;
p.fn_or_arrow_data_parse.allow_yield = old_fn_or_arrow_data.allow_yield;
p.fn_or_arrow_data_parse.needs_async_loc = old_fn_or_arrow_data.needs_async_loc;
p.fn_or_arrow_data_parse.allow_super_call = old_fn_or_arrow_data.allow_super_call;
p.fn_or_arrow_data_parse.allow_super_property =
old_fn_or_arrow_data.allow_super_property;
if let Some(decorator_scope) = opts.decorator_scope {
p.current_scope = decorator_scope;
}
let decorators = p.parse_type_script_decorators();
p.current_scope = inner_scope;
p.fn_or_arrow_data_parse = inner_fn_or_arrow_data;
ts_decorators = decorators?;
Comment thread
robobun marked this conversation as resolved.
if ts_decorators.len_u32() > 0 {
arg_has_decorators = true;
}
Expand Down
9 changes: 9 additions & 0 deletions src/js_parser/parse/parse_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
&ParseClassOptions {
allow_ts_decorators: Self::IS_TYPESCRIPT_ENABLED
|| p.options.features.standard_decorators,
is_class_expr: true,
..Default::default()
},
)?;
Expand All @@ -589,6 +590,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

fn pfx_t_at(p: &mut Self) -> PResult<Expr> {
// Parse decorators before a class expression: @dec class { ... }
if Self::IS_TYPESCRIPT_ENABLED && !p.options.features.standard_decorators {
p.log().add_range_error(
Some(p.source),
p.lexer.range(),
b"TypeScript experimental decorators cannot be used in expression position",
);
}
let ts_decorators = p.parse_type_script_decorators()?;

// Expect class keyword after decorators
Expand Down Expand Up @@ -650,6 +658,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
&ParseClassOptions {
ts_decorators: ts_decorators_slice,
allow_ts_decorators: true,
is_class_expr: true,
..Default::default()
},
)?;
Expand Down
11 changes: 7 additions & 4 deletions src/js_parser/parse/parse_property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
allow_super_call: opts.class_has_extends && is_constructor,
allow_super_property: true,
allow_ts_decorators: opts.allow_ts_decorators,
decorator_scope: opts.decorator_scope,
is_constructor,
has_decorators: opts.ts_decorators.len() > 0
|| (opts.has_class_decorators && is_constructor),
Expand Down Expand Up @@ -449,8 +450,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if let Some(prop) =
p.parse_property(kind, opts, None)?
{
if prop.kind == PropertyKind::Normal
&& prop.value.is_none()
if matches!(
prop.kind,
PropertyKind::Normal
| PropertyKind::AutoAccessor
) && prop.value.is_none()
&& opts.ts_decorators.len() > 0
{
let mut prop_ = prop;
Expand All @@ -463,10 +467,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
}
PropertyModifierKeyword::PAccessor => {
// "accessor" keyword for auto-accessor fields (TC39 standard decorators)
// "accessor" keyword for auto-accessor fields
if opts.is_class
&& !p.lexer.has_newline_before
&& p.options.features.standard_decorators
&& PropertyModifierKeyword::find(raw)
== Some(PropertyModifierKeyword::PAccessor)
{
Comment thread
robobun marked this conversation as resolved.
Expand Down
35 changes: 35 additions & 0 deletions src/js_parser/parse/parse_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,23 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// Parse decorators before class statements, which are potentially exported
if Self::IS_TYPESCRIPT_ENABLED || p.options.features.standard_decorators {
let scope_index = p.scopes_in_order.len();
let at_loc = p.lexer.loc();
let ts_decorators = p.parse_type_script_decorators()?;

// "@x export @y class Foo {}"
if opts.ts_decorators.is_some() {
p.log().add_range_error(
Some(p.source),
bun_ast::Range {
loc: at_loc,
len: p.lexer.range().end().start - at_loc.start,
},
b"Decorators are not valid here",
);
p.discard_scopes_up_to(scope_index);
return p.parse_stmt(opts);
}

// If this turns out to be a "declare class" statement, we need to undo the
// scopes that were potentially pushed while parsing the decorator arguments.
// That can look like any one of the following:
Expand Down Expand Up @@ -848,6 +863,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if opts.ts_decorators.is_some()
&& p.lexer.token != T::TClass
&& p.lexer.token != T::TDefault
&& p.lexer.token != T::TAt
&& !p.lexer.is_contextual_keyword(b"abstract")
&& !p.lexer.is_contextual_keyword(b"declare")
{
Expand Down Expand Up @@ -978,6 +994,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// "@decorator export default abstract class Foo {}"
if opts.ts_decorators.is_some()
&& p.lexer.token != T::TClass
&& p.lexer.token != T::TAt
&& !p.lexer.is_contextual_keyword(b"abstract")
{
p.lexer.expected(T::TClass)?;
Expand Down Expand Up @@ -1037,8 +1054,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
));
}

// "export default @x class Foo {}" is a class declaration too
if p.lexer.token == T::TFunction
|| p.lexer.token == T::TClass
|| p.lexer.token == T::TAt
|| p.lexer.is_contextual_keyword(b"interface")
{
let mut _opts = ParseStatementOptions {
Expand Down Expand Up @@ -1071,6 +1090,22 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
ref_: name.ref_,
};
}

// Lowered as an expression the class is named "default".
if class.class.should_lower_standard_decorators {
let mut class_ref = *class;
let class_value = core::mem::take(&mut class_ref.class);
let expr = p.new_expr(class_value, stmt.loc);
let default_name = p.create_default_name(default_loc);
p.has_es_module_syntax = true;
return Ok(p.s(
S::ExportDefault {
default_name,
value: js_ast::StmtOrExpr::Expr(expr),
},
loc,
));
}
}
// "interface" turned out not to start an interface
// declaration: the nested statement came back as an
Expand Down
9 changes: 9 additions & 0 deletions src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,9 @@

/// Allow TypeScript decorators in function arguments
pub(crate) allow_ts_decorators: bool,

/// The class body scope, where TypeScript parameter decorators are parsed.
pub(crate) decorator_scope: Option<js_ast::StoreRef<js_ast::Scope>>,
}

impl Default for FnOrArrowDataParse {
Expand All @@ -1237,6 +1240,7 @@
track_arrow_arg_errors: false,
allow_missing_body_for_type_script: false,
allow_ts_decorators: false,
decorator_scope: None,
}
}
}
Expand Down Expand Up @@ -1314,13 +1318,15 @@

// Class-related options
pub(crate) is_static: bool,
pub(crate) is_class: bool,

Check warning on line 1321 in src/js_parser/parser.rs

View workflow job for this annotation

GitHub Actions / mordant

`parser::PropertyOpts.is_class` is only ever written next to `decorator_scope`, `true` with `Some` and `false` with `None`, in all 2 places. It is `decorator_scope.is_some()` stored twice
pub(crate) class_has_extends: bool,
pub(crate) allow_ts_decorators: bool,
pub(crate) is_ts_abstract: bool,
pub(crate) ts_decorators: ExprNodeList,
pub(crate) has_argument_decorators: bool,
pub(crate) has_class_decorators: bool,
/// See `FnOrArrowDataParse::decorator_scope`.
pub(crate) decorator_scope: Option<js_ast::StoreRef<js_ast::Scope>>,
}

impl Default for PropertyOpts {
Expand All @@ -1338,6 +1344,7 @@
ts_decorators: bun_alloc::AstAlloc::vec(),
has_argument_decorators: false,
has_class_decorators: false,
decorator_scope: None,
}
}
}
Expand Down Expand Up @@ -1421,6 +1428,8 @@
pub(crate) ts_decorators: &'a [Expr],
pub(crate) allow_ts_decorators: bool,
pub(crate) is_type_script_declare: bool,
/// TypeScript experimental decorators are rejected inside class expressions.
pub(crate) is_class_expr: bool,
}

#[repr(u8)]
Expand Down
Loading
Loading