aboutsummaryrefslogtreecommitdiff
path: root/src/js_parser.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r--src/js_parser.zig890
1 files changed, 804 insertions, 86 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig
index a61657128..a9f346caf 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -70,7 +70,7 @@ const RefHashCtx = @import("./ast/base.zig").RefHashCtx;
pub const StringHashMap = bun.StringHashMap;
pub const AutoHashMap = std.AutoHashMap;
-const StringHashMapUnamanged = bun.StringHashMapUnmanaged;
+const StringHashMapUnmanaged = bun.StringHashMapUnmanaged;
const ObjectPool = @import("./pool.zig").ObjectPool;
const NodeFallbackModules = @import("./node_fallbacks.zig");
@@ -439,6 +439,155 @@ pub const TypeScript = struct {
};
}
+ pub const Metadata = union(enum) {
+ m_none: void,
+
+ m_never: void,
+ m_unknown: void,
+ m_any: void,
+ m_void: void,
+ m_null: void,
+ m_undefined: void,
+ m_function: void,
+ m_array: void,
+ m_boolean: void,
+ m_string: void,
+ m_object: void,
+ m_number: void,
+ m_bigint: void,
+ m_symbol: void,
+ m_promise: void,
+ m_identifier: Ref,
+ m_dot: List(Ref),
+
+ pub const default: @This() = .m_none;
+
+ // the logic in finishUnion, mergeUnion, finishIntersection and mergeIntersection is
+ // translated from:
+ // https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402
+
+ /// Return the final union type if possible, or return null to continue merging.
+ ///
+ /// If the current type is m_never, m_null, or m_undefined assign the current type
+ /// to m_none and return null to ensure it's always replaced by the next type.
+ pub fn finishUnion(current: *@This(), p: anytype) ?@This() {
+ return switch (current.*) {
+ .m_identifier => |ref| {
+ if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) {
+ return .m_object;
+ }
+ return null;
+ },
+
+ .m_unknown,
+ .m_any,
+ .m_object,
+ => .m_object,
+
+ .m_never,
+ .m_null,
+ .m_undefined,
+ => {
+ current.* = .m_none;
+ return null;
+ },
+
+ else => null,
+ };
+ }
+
+ pub fn mergeUnion(result: *@This(), left: @This()) void {
+ if (left != .m_none) {
+ if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) {
+ result.* = switch (result.*) {
+ .m_never,
+ .m_undefined,
+ .m_null,
+ => left,
+
+ else => .m_object,
+ };
+ } else {
+ switch (result.*) {
+ .m_identifier => |ref| {
+ if (!ref.eql(left.m_identifier)) {
+ result.* = .m_object;
+ }
+ },
+ else => {},
+ }
+ }
+ } else {
+ // always take the next value if left is m_none
+ }
+ }
+
+ /// Return the final intersection type if possible, or return null to continue merging.
+ ///
+ /// If the current type is m_unknown, m_null, or m_undefined assign the current type
+ /// to m_none and return null to ensure it's always replaced by the next type.
+ pub fn finishIntersection(current: *@This(), p: anytype) ?@This() {
+ return switch (current.*) {
+ .m_identifier => |ref| {
+ if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) {
+ return .m_object;
+ }
+ return null;
+ },
+
+ // ensure m_never is the final type
+ .m_never => .m_never,
+
+ .m_any,
+ .m_object,
+ => .m_object,
+
+ .m_unknown,
+ .m_null,
+ .m_undefined,
+ => {
+ current.* = .m_none;
+ return null;
+ },
+
+ else => null,
+ };
+ }
+
+ pub fn mergeIntersection(result: *@This(), left: @This()) void {
+ if (left != .m_none) {
+ if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) {
+ result.* = switch (result.*) {
+ .m_unknown,
+ .m_undefined,
+ .m_null,
+ => left,
+
+ // ensure m_never is the final type
+ .m_never => .m_never,
+
+ else => .m_object,
+ };
+ } else {
+ switch (result.*) {
+ .m_identifier => |ref| {
+ if (!ref.eql(left.m_identifier)) {
+ result.* = .m_object;
+ }
+ },
+ else => {},
+ }
+ }
+ } else {
+ // make sure intersection of only m_unknown serializes to "undefined"
+ // instead of "Object"
+ if (result.* == .m_unknown) {
+ result.* = .m_undefined;
+ }
+ }
+ }
+ };
+
pub fn isTSArrowFnJSX(p: anytype) !bool {
var oldLexer = std.mem.toBytes(p.lexer);
@@ -664,19 +813,19 @@ pub const TypeScript = struct {
.{ "abstract", .abstract },
.{ "asserts", .asserts },
- .{ "keyof", .prefix },
- .{ "readonly", .prefix },
+ .{ "keyof", .prefix_keyof },
+ .{ "readonly", .prefix_readonly },
- .{ "any", .primitive },
- .{ "never", .primitive },
- .{ "unknown", .primitive },
- .{ "undefined", .primitive },
- .{ "object", .primitive },
- .{ "number", .primitive },
- .{ "string", .primitive },
- .{ "boolean", .primitive },
- .{ "bigint", .primitive },
- .{ "symbol", .primitive },
+ .{ "any", .primitive_any },
+ .{ "never", .primitive_never },
+ .{ "unknown", .primitive_unknown },
+ .{ "undefined", .primitive_undefined },
+ .{ "object", .primitive_object },
+ .{ "number", .primitive_number },
+ .{ "string", .primitive_string },
+ .{ "boolean", .primitive_boolean },
+ .{ "bigint", .primitive_bigint },
+ .{ "symbol", .primitive_symbol },
.{ "infer", .infer },
});
@@ -685,17 +834,30 @@ pub const TypeScript = struct {
unique,
abstract,
asserts,
- prefix,
- primitive,
+ prefix_keyof,
+ prefix_readonly,
+ primitive_any,
+ primitive_never,
+ primitive_unknown,
+ primitive_undefined,
+ primitive_object,
+ primitive_number,
+ primitive_string,
+ primitive_boolean,
+ primitive_bigint,
+ primitive_symbol,
infer,
};
};
- pub const SkipTypeOptions = struct {
- is_return_type: bool = false,
- is_index_signature: bool = false,
- allow_tuple_labels: bool = false,
- disallow_conditional_types: bool = false,
+ pub const SkipTypeOptions = enum {
+ is_return_type,
+ is_index_signature,
+ allow_tuple_labels,
+ disallow_conditional_types,
+
+ pub const Bitset = std.enums.EnumSet(@This());
+ pub const empty = Bitset.initEmpty();
};
};
@@ -1398,8 +1560,9 @@ const StaticSymbolName = struct {
pub const __HMRClient = NewStaticSymbol("Bun");
pub const __FastRefreshModule = NewStaticSymbol("FastHMR");
pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh");
- pub const __decorateClass = NewStaticSymbol("__decorateClass");
- pub const __decorateParam = NewStaticSymbol("__decorateParam");
+ pub const __legacyDecorateClassTS = NewStaticSymbol("__legacyDecorateClassTS");
+ pub const __legacyDecorateParamTS = NewStaticSymbol("__legacyDecorateParamTS");
+ pub const __legacyMetadataTS = NewStaticSymbol("__legacyMetadataTS");
pub const @"$$typeof" = NewStaticSymbol("$$typeof");
pub const @"$$m" = NewStaticSymbol("$$m");
@@ -2437,6 +2600,7 @@ const FnOrArrowDataParse = struct {
is_typescript_declare: bool = false,
has_argument_decorators: bool = false,
+ has_decorators: bool = false,
is_return_disallowed: bool = false,
is_this_disallowed: bool = false,
@@ -2573,6 +2737,7 @@ const PropertyOpts = struct {
is_ts_abstract: bool = false,
ts_decorators: []Expr = &[_]Expr{},
has_argument_decorators: bool = false,
+ has_class_decorators: bool = false,
};
pub const ScanPassResult = struct {
@@ -4304,7 +4469,7 @@ fn NewParser_(
emitted_namespace_vars: RefMap = RefMap{},
is_exported_inside_namespace: RefRefMap = .{},
- known_enum_values: Map(Ref, StringHashMapUnamanged(f64)) = .{},
+ known_enum_values: Map(Ref, StringHashMapUnmanaged(f64)) = .{},
local_type_names: StringBoolMap = StringBoolMap{},
// This is the reference to the generated function argument for the namespace,
@@ -6737,7 +6902,7 @@ fn NewParser_(
try p.lexer.next();
if (p.lexer.token == T.t_colon) {
try p.lexer.next();
- try p.skipTypeScriptType(js_ast.Op.Level.lowest);
+ try p.skipTypeScriptType(.lowest);
}
if (p.lexer.token != T.t_comma) {
break;
@@ -6765,6 +6930,7 @@ fn NewParser_(
var is_identifier = p.lexer.token == T.t_identifier;
var text = p.lexer.identifier;
var arg = try p.parseBinding();
+ var ts_metadata = TypeScript.Metadata.default;
if (comptime is_typescript_enabled) {
if (is_identifier and opts.is_constructor) {
@@ -6804,7 +6970,11 @@ fn NewParser_(
// "function foo(a: any) {}"
if (p.lexer.token == .t_colon) {
try p.lexer.next();
- try p.skipTypeScriptType(.lowest);
+ if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators or arg_has_decorators)) {
+ ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest);
+ } else {
+ try p.skipTypeScriptType(.lowest);
+ }
}
}
@@ -6825,6 +6995,7 @@ fn NewParser_(
// We need to track this because it affects code generation
.is_typescript_ctor_field = is_typescript_ctor_field,
+ .ts_metadata = ts_metadata,
}) catch unreachable;
if (p.lexer.token != .t_comma) {
@@ -6864,9 +7035,18 @@ fn NewParser_(
p.fn_or_arrow_data_parse.has_argument_decorators = arg_has_decorators;
// "function foo(): any {}"
- if (is_typescript_enabled and p.lexer.token == .t_colon) {
- try p.lexer.next();
- try p.skipTypescriptReturnType();
+ if (is_typescript_enabled) {
+ if (p.lexer.token == .t_colon) {
+ try p.lexer.next();
+
+ if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) {
+ func.return_ts_metadata = try p.skipTypescriptReturnTypeWithMetadata();
+ } else {
+ try p.skipTypescriptReturnType();
+ }
+ } else if (func.flags.contains(.is_async) and p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) {
+ func.return_ts_metadata = .m_promise;
+ }
}
// "function foo(): any;"
@@ -6881,10 +7061,15 @@ fn NewParser_(
return func;
}
- // pub fn parseBinding(p: *P)
-
pub inline fn skipTypescriptReturnType(p: *P) anyerror!void {
- try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_return_type = true });
+ var result = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), false, &result);
+ }
+
+ pub inline fn skipTypescriptReturnTypeWithMetadata(p: *P) anyerror!TypeScript.Metadata {
+ var result = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), true, &result);
+ return result;
}
pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex {
@@ -6912,7 +7097,15 @@ fn NewParser_(
inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void {
p.markTypeScriptOnly();
- try p.skipTypeScriptTypeWithOpts(level, .{});
+ var result = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, false, &result);
+ }
+
+ inline fn skipTypeScriptTypeWithMetadata(p: *P, level: js_ast.Op.Level) anyerror!TypeScript.Metadata {
+ p.markTypeScriptOnly();
+ var result = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, true, &result);
+ return result;
}
fn skipTypeScriptBinding(p: *P) anyerror!void {
@@ -7057,41 +7250,77 @@ fn NewParser_(
// let x = (y: any): (y) => {return 0};
// let x = (y: any): asserts y is (y) => {};
//
- fn skipTypeScriptParenOrFnType(p: *P) anyerror!void {
+ fn skipTypeScriptParenOrFnType(p: *P, comptime get_metadata: bool, result: *TypeScript.Metadata) anyerror!void {
p.markTypeScriptOnly();
if (p.trySkipTypeScriptArrowArgsWithBacktracking()) {
try p.skipTypescriptReturnType();
+ if (comptime get_metadata)
+ result.* = .m_function;
} else {
try p.lexer.expect(.t_open_paren);
- try p.skipTypeScriptType(.lowest);
+ if (comptime get_metadata) {
+ result.* = try p.skipTypeScriptTypeWithMetadata(.lowest);
+ } else {
+ try p.skipTypeScriptType(.lowest);
+ }
try p.lexer.expect(.t_close_paren);
}
}
- fn skipTypeScriptTypeWithOpts(p: *P, level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions) anyerror!void {
+ fn skipTypeScriptTypeWithOpts(
+ p: *P,
+ level: js_ast.Op.Level,
+ opts: TypeScript.SkipTypeOptions.Bitset,
+ comptime get_metadata: bool,
+ result: *TypeScript.Metadata,
+ ) anyerror!void {
p.markTypeScriptOnly();
while (true) {
switch (p.lexer.token) {
- .t_numeric_literal,
- .t_big_integer_literal,
- .t_string_literal,
- .t_no_substitution_template_literal,
- .t_true,
- .t_false,
- .t_null,
- .t_void,
- => {
+ .t_numeric_literal => {
try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_number;
+ }
+ },
+ .t_big_integer_literal => {
+ try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_bigint;
+ }
+ },
+ .t_string_literal, .t_no_substitution_template_literal => {
+ try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_string;
+ }
+ },
+ .t_true, .t_false => {
+ try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_boolean;
+ }
+ },
+ .t_null => {
+ try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_null;
+ }
+ },
+ .t_void => {
+ try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_void;
+ }
},
-
.t_const => {
const r = p.lexer.range();
try p.lexer.next();
// ["const: number]"
- if (opts.allow_tuple_labels and p.lexer.token == .t_colon) {
+ if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) {
try p.log.addRangeError(p.source, r, "Unexpected \"const\"");
}
},
@@ -7105,6 +7334,10 @@ fn NewParser_(
try p.skipTypeScriptType(.lowest);
return;
}
+
+ if (comptime get_metadata) {
+ result.* = .m_object;
+ }
},
.t_minus => {
// "-123"
@@ -7113,8 +7346,14 @@ fn NewParser_(
if (p.lexer.token == .t_big_integer_literal) {
try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_bigint;
+ }
} else {
try p.lexer.expect(.t_numeric_literal);
+ if (comptime get_metadata) {
+ result.* = .m_number;
+ }
}
},
.t_ampersand, .t_bar => {
@@ -7127,7 +7366,7 @@ fn NewParser_(
try p.lexer.next();
// "[import: number]"
- if (opts.allow_tuple_labels and p.lexer.token == .t_colon) {
+ if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) {
return;
}
@@ -7155,21 +7394,21 @@ fn NewParser_(
try p.lexer.next();
// "[new: number]"
- if (opts.allow_tuple_labels and p.lexer.token == .t_colon) {
+ if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) {
return;
}
_ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true });
- try p.skipTypeScriptParenOrFnType();
+ try p.skipTypeScriptParenOrFnType(get_metadata, result);
},
.t_less_than => {
// "<T>() => Foo<T>"
_ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true });
- try p.skipTypeScriptParenOrFnType();
+ try p.skipTypeScriptParenOrFnType(get_metadata, result);
},
.t_open_paren => {
// "(number | string)"
- try p.skipTypeScriptParenOrFnType();
+ try p.skipTypeScriptParenOrFnType(get_metadata, result);
},
.t_identifier => {
const kind = TypeScript.Identifier.IMap.get(p.lexer.identifier) orelse .normal;
@@ -7177,7 +7416,7 @@ fn NewParser_(
var check_type_parameters = true;
switch (kind) {
- .prefix => {
+ .prefix_keyof => {
try p.lexer.next();
// Valid:
@@ -7188,10 +7427,28 @@ fn NewParser_(
// Invalid:
// "A extends B ? keyof : string"
//
- if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) {
+ if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) {
try p.skipTypeScriptType(.prefix);
}
+ if (comptime get_metadata) {
+ result.* = .m_object;
+ }
+
+ break;
+ },
+ .prefix_readonly => {
+ try p.lexer.next();
+
+ if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) {
+ try p.skipTypeScriptType(.prefix);
+ }
+
+ // assume array or tuple literal
+ if (comptime get_metadata) {
+ result.* = .m_array;
+ }
+
break;
},
.infer => {
@@ -7201,7 +7458,7 @@ fn NewParser_(
// "type Foo = Bar extends [infer T extends string] ? T : null"
// "type Foo = Bar extends [infer T extends string ? infer T : never] ? T : null"
// "type Foo = { [infer in Bar]: number }"
- if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) {
+ if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) {
try p.lexer.expect(.t_identifier);
if (p.lexer.token == .t_extends) {
_ = p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(opts);
@@ -7232,15 +7489,90 @@ fn NewParser_(
// "function assert(x: boolean): asserts x"
// "function assert(x: boolean): asserts x is boolean"
- if (opts.is_return_type and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) {
+ if (opts.contains(.is_return_type) and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) {
try p.lexer.next();
}
},
- .primitive => {
+ .primitive_any => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_any;
+ }
+ },
+ .primitive_never => {
try p.lexer.next();
check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_never;
+ }
+ },
+ .primitive_unknown => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_unknown;
+ }
+ },
+ .primitive_undefined => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_undefined;
+ }
+ },
+ .primitive_object => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_object;
+ }
+ },
+ .primitive_number => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_number;
+ }
+ },
+ .primitive_string => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_string;
+ }
+ },
+ .primitive_boolean => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_boolean;
+ }
+ },
+ .primitive_bigint => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_bigint;
+ }
+ },
+ .primitive_symbol => {
+ try p.lexer.next();
+ check_type_parameters = false;
+ if (comptime get_metadata) {
+ result.* = .m_symbol;
+ }
},
else => {
+ if (comptime get_metadata) {
+ const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable;
+ if (p.known_enum_values.contains(find_result.ref)) {
+ result.* = .m_number;
+ } else {
+ result.* = .{ .m_identifier = find_result.ref };
+ }
+ }
+
try p.lexer.next();
},
}
@@ -7261,10 +7593,15 @@ fn NewParser_(
try p.lexer.next();
// "[typeof: number]"
- if (opts.allow_tuple_labels and p.lexer.token == .t_colon) {
+ if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) {
return;
}
+ // always `Object`
+ if (comptime get_metadata) {
+ result.* = .m_object;
+ }
+
if (p.lexer.token == .t_import) {
// "typeof import('fs')"
continue;
@@ -7296,13 +7633,16 @@ fn NewParser_(
// "[first: number, second: string]"
try p.lexer.next();
+ if (comptime get_metadata) {
+ result.* = .m_array;
+ }
+
while (p.lexer.token != .t_close_bracket) {
if (p.lexer.token == .t_dot_dot_dot) {
try p.lexer.next();
}
- try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions{
- .allow_tuple_labels = true,
- });
+ var dummy_result = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.allow_tuple_labels), false, &dummy_result);
if (p.lexer.token == .t_question) {
try p.lexer.next();
}
@@ -7319,10 +7659,12 @@ fn NewParser_(
},
.t_open_brace => {
try p.skipTypeScriptObjectType();
+ if (comptime get_metadata) {
+ result.* = .m_object;
+ }
},
.t_template_head => {
// "`${'a' | 'b'}-${'c' | 'd'}`"
-
while (true) {
try p.lexer.next();
try p.skipTypeScriptType(.lowest);
@@ -7333,11 +7675,14 @@ fn NewParser_(
break;
}
}
+ if (comptime get_metadata) {
+ result.* = .m_string;
+ }
},
else => {
// "[function: number]"
- if (opts.allow_tuple_labels and p.lexer.isIdentifierOrKeyword()) {
+ if (opts.contains(.allow_tuple_labels) and p.lexer.isIdentifierOrKeyword()) {
if (p.lexer.token != .t_function) {
try p.lexer.unexpected();
}
@@ -7362,8 +7707,22 @@ fn NewParser_(
if (level.gte(.bitwise_or)) {
return;
}
+
try p.lexer.next();
- try p.skipTypeScriptType(.bitwise_or);
+
+ if (comptime get_metadata) {
+ var left = result.*;
+ if (left.finishUnion(p)) |final| {
+ // finish skipping the rest of the type without collecting type metadata.
+ result.* = final;
+ try p.skipTypeScriptType(.bitwise_or);
+ } else {
+ try p.skipTypeScriptTypeWithOpts(.bitwise_or, TypeScript.SkipTypeOptions.empty, get_metadata, result);
+ result.mergeUnion(left);
+ }
+ } else {
+ try p.skipTypeScriptType(.bitwise_or);
+ }
},
.t_ampersand => {
if (level.gte(.bitwise_and)) {
@@ -7371,7 +7730,20 @@ fn NewParser_(
}
try p.lexer.next();
- try p.skipTypeScriptType(.bitwise_and);
+
+ if (comptime get_metadata) {
+ var left = result.*;
+ if (left.finishIntersection(p)) |final| {
+ // finish skipping the rest of the type without collecting type metadata.
+ result.* = final;
+ try p.skipTypeScriptType(.bitwise_and);
+ } else {
+ try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result);
+ result.mergeIntersection(left);
+ }
+ } else {
+ try p.skipTypeScriptType(.bitwise_and);
+ }
},
.t_exclamation => {
// A postfix "!" is allowed in JSDoc types in TypeScript, which are only
@@ -7390,6 +7762,22 @@ fn NewParser_(
if (!p.lexer.isIdentifierOrKeyword()) {
try p.lexer.expect(.t_identifier);
}
+
+ if (comptime get_metadata) {
+ if (result.* == .m_identifier) {
+ var dot = List(Ref).initCapacity(p.allocator, 2) catch unreachable;
+ dot.appendAssumeCapacity(result.m_identifier);
+ const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable;
+ dot.appendAssumeCapacity(find_result.ref);
+ result.* = .{ .m_dot = dot };
+ } else if (result.* == .m_dot) {
+ if (p.lexer.isIdentifierOrKeyword()) {
+ const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable;
+ result.m_dot.append(p.allocator, find_result.ref) catch unreachable;
+ }
+ }
+ }
+
try p.lexer.next();
// "{ <A extends B>(): c.d \n <E extends F>(): g.h }" must not become a single type
@@ -7403,26 +7791,56 @@ fn NewParser_(
return;
}
try p.lexer.next();
+ var skipped = false;
if (p.lexer.token != .t_close_bracket) {
+ skipped = true;
try p.skipTypeScriptType(.lowest);
}
try p.lexer.expect(.t_close_bracket);
+
+ if (comptime get_metadata) {
+ if (result.* == .m_none) {
+ result.* = .m_array;
+ } else {
+ // if something was skipped, it is object type
+ if (skipped) {
+ result.* = .m_object;
+ } else {
+ result.* = .m_array;
+ }
+ }
+ }
},
.t_extends => {
// "{ x: number \n extends: boolean }" must not become a single type
- if (p.lexer.has_newline_before or opts.disallow_conditional_types) {
+ if (p.lexer.has_newline_before or opts.contains(.disallow_conditional_types)) {
return;
}
try p.lexer.next();
// The type following "extends" is not permitted to be another conditional type
- try p.skipTypeScriptTypeWithOpts(.lowest, .{ .disallow_conditional_types = true });
+ var extends_type = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), get_metadata, &extends_type);
- try p.lexer.expect(.t_question);
- try p.skipTypeScriptType(.lowest);
- try p.lexer.expect(.t_colon);
- try p.skipTypeScriptType(.lowest);
+ if (comptime get_metadata) {
+ // intersection
+ try p.lexer.expect(.t_question);
+ var left = try p.skipTypeScriptTypeWithMetadata(.lowest);
+ try p.lexer.expect(.t_colon);
+ if (left.finishIntersection(p)) |final| {
+ result.* = final;
+ try p.skipTypeScriptType(.lowest);
+ } else {
+ try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result);
+ result.mergeIntersection(left);
+ }
+ } else {
+ try p.lexer.expect(.t_question);
+ try p.skipTypeScriptType(.lowest);
+ try p.lexer.expect(.t_colon);
+ try p.skipTypeScriptType(.lowest);
+ }
},
else => {
return;
@@ -7452,7 +7870,8 @@ fn NewParser_(
if (p.lexer.token == .t_open_bracket) {
// Index signature or computed property
try p.lexer.next();
- try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_index_signature = true });
+ var metadata = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_index_signature), false, &metadata);
// "{ [key: string]: number }"
// "{ readonly [K in keyof T]: T[K] }"
@@ -11122,13 +11541,12 @@ fn NewParser_(
return result;
}
- pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) anyerror!bool {
+ pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) anyerror!bool {
try p.lexer.expect(.t_extends);
- try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions{
- .disallow_conditional_types = true,
- });
+ var metadata = TypeScript.Metadata.default;
+ try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), false, &metadata);
- if (!flags.disallow_conditional_types and p.lexer.token == .t_question) {
+ if (!flags.contains(.disallow_conditional_types) and p.lexer.token == .t_question) {
return error.Backtrack;
}
@@ -11181,7 +11599,7 @@ fn NewParser_(
return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowArgsWithBacktracking, bool);
}
- pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) bool {
+ pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) bool {
return Backtracking.lexerBacktrackerWithArgs(p, Backtracking.skipTypeScriptConstraintOfInferTypeWithBacktracking, .{ p, flags }, bool);
}
@@ -11621,6 +12039,7 @@ fn NewParser_(
(p.lexer.token != .t_open_paren or has_definite_assignment_assertion_operator))
{
var initializer: ?Expr = null;
+ var ts_metadata = TypeScript.Metadata.default;
// Forbid the names "constructor" and "prototype" in some cases
if (!is_computed) {
@@ -11639,7 +12058,11 @@ fn NewParser_(
// Skip over types
if (p.lexer.token == .t_colon) {
try p.lexer.next();
- try p.skipTypeScriptType(.lowest);
+ if (p.options.features.emit_decorator_metadata and opts.is_class and opts.ts_decorators.len > 0) {
+ ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest);
+ } else {
+ try p.skipTypeScriptType(.lowest);
+ }
}
}
@@ -11693,6 +12116,7 @@ fn NewParser_(
}),
.key = key,
.initializer = initializer,
+ .ts_metadata = ts_metadata,
};
}
@@ -11739,6 +12163,7 @@ fn NewParser_(
.allow_super_property = true,
.allow_ts_decorators = opts.allow_ts_decorators,
.is_constructor = is_constructor,
+ .has_decorators = opts.ts_decorators.len > 0 or (opts.has_class_decorators and is_constructor),
// Only allow omitting the body if we're parsing TypeScript class
.allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class,
@@ -11816,6 +12241,7 @@ fn NewParser_(
}),
.key = key,
.value = value,
+ .ts_metadata = .m_function,
};
}
@@ -11895,6 +12321,7 @@ fn NewParser_(
const first_decorator_loc = p.lexer.loc();
if (opts.allow_ts_decorators) {
opts.ts_decorators = try p.parseTypeScriptDecorators();
+ opts.has_class_decorators = class_opts.ts_decorators.len > 0;
has_decorators = has_decorators or opts.ts_decorators.len > 0;
} else {
opts.ts_decorators = &[_]Expr{};
@@ -18123,7 +18550,7 @@ fn NewParser_(
// Track values so they can be used by constant folding. We need to follow
// links here in case the enum was merged with a preceding namespace
- var values_so_far = StringHashMapUnamanged(f64){};
+ var values_so_far = StringHashMapUnmanaged(f64){};
p.known_enum_values.put(allocator, data.name.ref orelse p.panic("Expected data.name.ref", .{}), values_so_far) catch unreachable;
p.known_enum_values.put(allocator, data.arg, values_so_far) catch unreachable;
@@ -18765,7 +19192,7 @@ fn NewParser_(
const args = p.allocator.alloc(Expr, 2) catch unreachable;
args[0] = p.newExpr(E.Number{ .value = @as(f64, @floatFromInt(i)) }, arg_decorator.loc);
args[1] = arg_decorator;
- decorators.append(p.callRuntime(arg_decorator.loc, "__decorateParam", args)) catch unreachable;
+ decorators.append(p.callRuntime(arg_decorator.loc, "__legacyDecorateParamTS", args)) catch unreachable;
if (is_constructor) {
class.ts_decorators.update(decorators);
} else {
@@ -18792,7 +19219,12 @@ fn NewParser_(
else => bun.unreachablePanic("Unexpected AST node type {any}", .{prop.key.?}),
};
- const descriptor_kind: f64 = if (!prop.flags.contains(.is_method)) 2 else 1;
+ // TODO: when we have the `accessor` modifier, add `and !prop.flags.contains(.has_accessor_modifier)` to
+ // the if statement.
+ const descriptor_kind: Expr = if (!prop.flags.contains(.is_method))
+ p.newExpr(E.Undefined{}, loc)
+ else
+ p.newExpr(E.Null{}, loc);
var target: Expr = undefined;
if (prop.flags.contains(.is_static)) {
@@ -18802,13 +19234,60 @@ fn NewParser_(
target = p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), .name = "prototype", .name_loc = loc }, loc);
}
+ var array = prop.ts_decorators.listManaged(p.allocator);
+
+ if (p.options.features.emit_decorator_metadata) {
+ {
+ // design:type
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty);
+ args[1] = p.serializeMetadata(prop.ts_metadata) catch unreachable;
+ array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
+ }
+ {
+ // design:paramtypes and design:returntype if method
+ if (prop.flags.contains(.is_method)) {
+ if (prop.value) |prop_value| {
+ {
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
+
+ const method_args = prop_value.data.e_function.func.args;
+
+ if (method_args.len > 0) {
+ var args_array = p.allocator.alloc(Expr, method_args.len) catch unreachable;
+
+ for (method_args, 0..) |method_arg, i| {
+ args_array[i] = p.serializeMetadata(method_arg.ts_metadata) catch unreachable;
+ }
+
+ args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(args_array) }, logger.Loc.Empty);
+ } else {
+ args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty);
+ }
+
+ array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
+ }
+ {
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.newExpr(E.String{ .data = "design:returntype" }, logger.Loc.Empty);
+
+ args[1] = p.serializeMetadata(prop_value.data.e_function.func.return_ts_metadata) catch unreachable;
+
+ array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
+ }
+ }
+ }
+ }
+ }
+
const args = p.allocator.alloc(Expr, 4) catch unreachable;
- args[0] = p.newExpr(E.Array{ .items = prop.ts_decorators }, loc);
+ args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, loc);
args[1] = target;
args[2] = descriptor_key;
- args[3] = p.newExpr(E.Number{ .value = descriptor_kind }, loc);
+ args[3] = descriptor_kind;
- const decorator = p.callRuntime(prop.key.?.loc, "__decorateClass", args);
+ const decorator = p.callRuntime(prop.key.?.loc, "__legacyDecorateClassTS", args);
const decorator_stmt = p.s(S.SExpr{ .value = decorator }, decorator.loc);
if (prop.flags.contains(.is_static)) {
@@ -18916,13 +19395,38 @@ fn NewParser_(
stmts.appendSliceAssumeCapacity(instance_decorators.items);
stmts.appendSliceAssumeCapacity(static_decorators.items);
if (class.ts_decorators.len > 0) {
+ var array = class.ts_decorators.listManaged(p.allocator);
+
+ if (p.options.features.emit_decorator_metadata) {
+ if (constructor_function != null) {
+ // design:paramtypes
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
+
+ const constructor_args = constructor_function.?.func.args;
+ if (constructor_args.len > 0) {
+ var param_array = p.allocator.alloc(Expr, constructor_args.len) catch unreachable;
+
+ for (constructor_args, 0..) |constructor_arg, i| {
+ param_array[i] = p.serializeMetadata(constructor_arg.ts_metadata) catch unreachable;
+ }
+
+ args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(param_array) }, logger.Loc.Empty);
+ } else {
+ args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty);
+ }
+
+ array.append(p.callRuntime(stmt.loc, "__legacyMetadataTS", args)) catch unreachable;
+ }
+ }
+
const args = p.allocator.alloc(Expr, 2) catch unreachable;
- args[0] = p.newExpr(E.Array{ .items = class.ts_decorators }, stmt.loc);
+ args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, stmt.loc);
args[1] = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
stmts.appendAssumeCapacity(Stmt.assign(
p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc),
- p.callRuntime(stmt.loc, "__decorateClass", args),
+ p.callRuntime(stmt.loc, "__legacyDecorateClassTS", args),
p.allocator,
));
@@ -18939,6 +19443,220 @@ fn NewParser_(
}
}
+ fn serializeMetadata(p: *P, ts_metadata: TypeScript.Metadata) !Expr {
+ return switch (ts_metadata) {
+ .m_none => p.newExpr(
+ E.Undefined{},
+ logger.Loc.Empty,
+ ),
+
+ .m_any,
+ .m_unknown,
+ .m_object,
+ => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+
+ .m_never,
+ .m_undefined,
+ .m_null,
+ .m_void,
+ => p.newExpr(
+ E.Undefined{},
+ logger.Loc.Empty,
+ ),
+
+ .m_string => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "String") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .m_number => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Number") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .m_function => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Function") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .m_boolean => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Boolean") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .m_array => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Array") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+
+ .m_bigint => p.maybeDefinedHelper(
+ p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "BigInt") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ ),
+
+ .m_symbol => p.maybeDefinedHelper(
+ p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Symbol") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ ),
+
+ .m_promise => p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Promise") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+
+ .m_identifier => |ref| {
+ p.recordUsage(ref);
+ return p.maybeDefinedHelper(p.newExpr(
+ E.Identifier{ .ref = ref },
+ logger.Loc.Empty,
+ ));
+ },
+
+ .m_dot => |_refs| {
+ var refs = _refs;
+ std.debug.assert(refs.items.len >= 2);
+ defer refs.deinit(p.allocator);
+
+ var dots = p.newExpr(
+ E.Dot{
+ .name = p.loadNameFromRef(refs.items[refs.items.len - 1]),
+ .name_loc = logger.Loc.Empty,
+ .target = undefined,
+ },
+ logger.Loc.Empty,
+ );
+
+ var current_expr = &dots.data.e_dot.target;
+ var i: usize = refs.items.len - 2;
+ while (i > 0) {
+ current_expr.* = p.newExpr(E.Dot{
+ .name = p.loadNameFromRef(refs.items[i]),
+ .name_loc = logger.Loc.Empty,
+ .target = undefined,
+ }, logger.Loc.Empty);
+ current_expr = &current_expr.data.e_dot.target;
+ i -= 1;
+ }
+
+ current_expr.* = p.newExpr(
+ E.Identifier{
+ .ref = refs.items[0],
+ },
+ logger.Loc.Empty,
+ );
+
+ const dot_identifier = current_expr.*;
+ var current_dot = dots;
+
+ var maybe_defined_dots = p.newExpr(
+ E.Binary{
+ .op = .bin_logical_or,
+ .right = try p.checkIfDefinedHelper(current_dot),
+ .left = undefined,
+ },
+ logger.Loc.Empty,
+ );
+
+ if (i < refs.items.len - 2) {
+ current_dot = current_dot.data.e_dot.target;
+ }
+ current_expr = &maybe_defined_dots.data.e_binary.left;
+
+ while (i < refs.items.len - 2) {
+ current_expr.* = p.newExpr(
+ E.Binary{
+ .op = .bin_logical_or,
+ .right = try p.checkIfDefinedHelper(current_dot),
+ .left = undefined,
+ },
+ logger.Loc.Empty,
+ );
+
+ current_expr = &current_expr.data.e_binary.left;
+ i += 1;
+ if (i < refs.items.len - 2) {
+ current_dot = current_dot.data.e_dot.target;
+ }
+ }
+
+ current_expr.* = try p.checkIfDefinedHelper(dot_identifier);
+
+ var root = p.newExpr(
+ E.If{
+ .yes = p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .no = dots,
+ .test_ = maybe_defined_dots,
+ },
+ logger.Loc.Empty,
+ );
+
+ return root;
+ },
+ };
+ }
+
+ fn checkIfDefinedHelper(p: *P, expr: Expr) !Expr {
+ return p.newExpr(
+ E.Binary{
+ .op = .bin_strict_eq,
+ .left = p.newExpr(
+ E.Unary{
+ .op = .un_typeof,
+ .value = expr,
+ },
+ logger.Loc.Empty,
+ ),
+ .right = p.newExpr(
+ E.String{ .data = "undefined" },
+ logger.Loc.Empty,
+ ),
+ },
+ logger.Loc.Empty,
+ );
+ }
+
+ fn maybeDefinedHelper(p: *P, identifier_expr: Expr) !Expr {
+ return p.newExpr(
+ E.If{
+ .test_ = try p.checkIfDefinedHelper(identifier_expr),
+ .yes = p.newExpr(
+ E.Identifier{
+ .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
+ },
+ logger.Loc.Empty,
+ ),
+ .no = identifier_expr,
+ },
+ logger.Loc.Empty,
+ );
+ }
+
fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt {
switch (stmt.data) {
.s_expr => |st| {