aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json21
-rw-r--r--src/fs.zig1
-rw-r--r--src/js_ast.zig300
-rw-r--r--src/js_lexer_tables.zig12
-rw-r--r--src/js_parser.zig511
-rw-r--r--src/logger.zig2
6 files changed, 778 insertions, 69 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index b59ff50a8..9f483394d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -3,16 +3,21 @@
"configurations": [
{
"name": "Test",
- "type": "cppdbg",
+ "type": "lldb",
"request": "launch",
- "program": "${workspaceFolder}/zig-cache/bin/test",
- "args": ["prevent-panic-by-passing-a-placeholder-arg"],
- "preLaunchTask": "test",
- "stopAtEntry": false,
+ "stdio": null,
+ "stopOnEntry": false,
+ "program": "/usr/local/bin/zig",
"cwd": "${workspaceFolder}",
- "environment": [],
- "externalConsole": false,
- "MIMode": "lldb"
+ "args": ["test", "${file}"],
+ "presentation": {
+ "hidden": false,
+ "group": "",
+ "order": 1
+ },
+ "env": {
+ "TERM": "xterm"
+ }
},
{
diff --git a/src/fs.zig b/src/fs.zig
index a74d3478d..4f2918412 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -106,6 +106,7 @@ test "PathName.init" {
const res = PathName.init(
&file,
);
+
std.testing.expectEqualStrings(res.dir, "/root/directory");
std.testing.expectEqualStrings(res.base, "file");
std.testing.expectEqualStrings(res.ext, ".ext");
diff --git a/src/js_ast.zig b/src/js_ast.zig
index fb494b909..813e76c0e 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -26,6 +26,10 @@ pub const BindingNodeIndex = *Binding;
pub const StmtNodeIndex = *Stmt;
pub const ExprNodeIndex = *Expr;
+pub const ExprNodeList = []Expr;
+pub const StmtNodeList = []Stmt;
+pub const BindingNodeList = []Binding;
+
// TODO: figure out if we actually need this
// -- original comment --
// Files are parsed in parallel for speed. We want to allow each parser to
@@ -40,10 +44,10 @@ pub const ExprNodeIndex = *Expr;
// The maps can be merged quickly by creating a single outer array containing
// all inner arrays from all parsed files.
pub const Ref = struct {
- source_index: u32 = 0,
+ source_index: ?u32 = null,
inner_index: u32,
- const None = Ref{ .source_index = std.math.maxInt(u32), .inner_index = std.math.maxInt(u32) };
+ const None = Ref{ .source_index = null, .inner_index = std.math.maxInt(u32) };
};
pub const ImportItemStatus = enum(u8) {
@@ -59,23 +63,48 @@ pub const ImportItemStatus = enum(u8) {
pub const LocRef = struct { loc: logger.Loc, ref: ?Ref };
pub const Binding = struct {
+ loc: logger.Loc,
data: B,
-};
-
-pub const B = union(enum) {
- identifier: B.Identifier,
- array: B.Array,
- property: B.Property,
- object: B.Object,
- missing: B.Missing,
- pub const Type = enum {
- b_missing,
+ pub const Tag = enum {
b_identifier,
b_array,
+ b_property,
b_object,
+ b_missing,
};
+ pub fn init(t: anytype, loc: logger.Loc) Binding {
+ switch (@TypeOf(t)) {
+ B.Identifier => {
+ return Binding{ .loc = loc, .data = B{ .b_identifier = t } };
+ },
+ B.Array => {
+ return Binding{ .loc = loc, .data = B{ .b_array = t } };
+ },
+ B.Property => {
+ return Binding{ .loc = loc, .data = B{ .b_property = t } };
+ },
+ B.Object => {
+ return Binding{ .loc = loc, .data = B{ .b_object = t } };
+ },
+ B.Missing => {
+ return Binding{ .loc = loc, .data = B{ .b_missing = t } };
+ },
+ else => {
+ @compileError("Invalid type passed to Binding.init");
+ },
+ }
+ }
+};
+
+pub const B = union(Binding.Tag) {
+ b_identifier: B.Identifier,
+ b_array: B.Array,
+ b_property: B.Property,
+ b_object: B.Object,
+ b_missing: B.Missing,
+
pub const Identifier = struct {
ref: Ref,
};
@@ -89,7 +118,7 @@ pub const B = union(enum) {
};
key: ExprNodeIndex,
- value: ?BindingNodeIndex,
+ value: ?BindingNodeIndex = null,
kind: Kind = Kind.normal,
initializer: ?ExprNodeIndex,
is_computed: bool = false,
@@ -100,7 +129,11 @@ pub const B = union(enum) {
pub const Object = struct { properties: []Property };
- pub const Array = struct { binding: BindingNodeIndex, default_value: ?Expr };
+ pub const Array = struct {
+ items: []ArrayBinding,
+ has_spread: bool = false,
+ is_single_line: bool = false,
+ };
pub const Missing = struct {};
};
@@ -134,7 +167,7 @@ pub const G = struct {
pub const Class = struct {
class_keyword: logger.Range,
- ts_decorators: ?[]ExprNodeIndex = null,
+ ts_decorators: ?ExprNodeList = null,
name: logger.Loc,
extends: ?ExprNodeIndex = null,
body_loc: logger.Loc,
@@ -145,7 +178,7 @@ pub const G = struct {
pub const Comment = struct { loc: logger.Loc, text: string };
pub const Property = struct {
- ts_decorators: []ExprNodeIndex,
+ ts_decorators: ExprNodeList,
key: ExprNodeIndex,
// This is omitted for class fields
@@ -170,7 +203,7 @@ pub const G = struct {
pub const FnBody = struct {
loc: logger.Loc,
- stmts: []StmtNodeIndex,
+ stmts: StmtNodeList,
};
pub const Fn = struct {
@@ -178,7 +211,7 @@ pub const G = struct {
open_parens_loc: logger.Loc,
args: ?[]Arg = null,
body: ?FnBody = null,
- arguments_ref: ?Ref,
+ arguments_ref: ?Ref = null,
is_async: bool = false,
is_generator: bool = false,
@@ -190,7 +223,7 @@ pub const G = struct {
};
pub const Arg = struct {
- ts_decorators: ?[]ExprNodeIndex = null,
+ ts_decorators: ?ExprNodeList = null,
binding: BindingNodeIndex,
default: ?ExprNodeIndex = null,
@@ -222,7 +255,7 @@ pub const Symbol = struct {
// form a linked-list where the last link is the symbol to use. This link is
// an invalid ref if it's the last link. If this isn't invalid, you need to
// FollowSymbols to get the real one.
- link: ?Ref,
+ link: ?Ref = null,
// An estimate of the number of uses of this symbol. This is used to detect
// whether a symbol is used or not. For example, TypeScript imports that are
@@ -247,7 +280,7 @@ pub const Symbol = struct {
// slot namespaces: regular symbols, label symbols, and private symbols.
nested_scope_slot: ?u32 = null,
- kind: Kind,
+ kind: Kind = Kind.other,
// Certain symbols must not be renamed or minified. For example, the
// "arguments" variable is declared by the runtime for every function.
@@ -410,7 +443,7 @@ pub const Symbol = struct {
};
pub const Use = struct {
- count_estimate: u32,
+ count_estimate: u32 = 0,
};
pub const Map = struct {
@@ -465,10 +498,10 @@ ccontinue };
pub const E = struct {
pub const Array = struct {
- items: []ExprNodeIndex,
- comma_after_spread: logger.Loc,
- is_single_line: bool,
- is_parenthesized: bool,
+ items: ExprNodeList,
+ comma_after_spread: ?logger.Loc = null,
+ is_single_line: bool = false,
+ is_parenthesized: bool = false,
};
pub const Unary = struct {
@@ -488,7 +521,7 @@ pub const E = struct {
pub const Undefined = struct {};
pub const New = struct {
target: ExprNodeIndex,
- args: []ExprNodeIndex,
+ args: ExprNodeList,
// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding
// this call expression. See the comment inside ECall for more details.
@@ -500,8 +533,8 @@ pub const E = struct {
pub const Call = struct {
// Node:
target: ExprNodeIndex,
- args: []ExprNodeIndex,
- optional_chain: OptionalChain,
+ args: ExprNodeList,
+ optional_chain: ?OptionalChain = null,
is_direct_eval: bool = false,
// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding
@@ -525,7 +558,7 @@ pub const E = struct {
// target is Node
name: string,
name_loc: logger.Loc,
- optional_chain: ?OptionalChain,
+ optional_chain: ?OptionalChain = null,
// If true, this property access is known to be free of side-effects. That
// means it can be removed if the resulting value isn't used.
@@ -545,7 +578,7 @@ pub const E = struct {
pub const Index = struct {
index: ExprNodeIndex,
- optional_chain: ?OptionalChain,
+ optional_chain: ?OptionalChain = null,
pub fn hasSameFlagsAs(a: *Index, b: *Index) bool {
return (a.optional_chain == b.optional_chain);
@@ -619,9 +652,9 @@ pub const E = struct {
};
pub const JSXElement = struct {
- tag: ?ExprNodeIndex,
+ tag: ?ExprNodeIndex = null,
properties: []G.Property,
- children: []ExprNodeIndex,
+ children: ExprNodeList,
};
pub const Missing = struct {};
@@ -634,17 +667,17 @@ pub const E = struct {
pub const Object = struct {
properties: []G.Property,
- comma_after_spread: logger.Loc,
- is_single_line: bool,
- is_parenthesized: bool,
+ comma_after_spread: ?logger.Loc = null,
+ is_single_line: bool = false,
+ is_parenthesized: bool = false,
};
pub const Spread = struct { value: ExprNodeIndex };
pub const String = struct {
value: JavascriptString,
- legacy_octal_loc: logger.Loc,
- prefer_template: bool,
+ legacy_octal_loc: ?logger.Loc = null,
+ prefer_template: bool = false,
};
// value is in the Node
@@ -655,8 +688,8 @@ pub const E = struct {
tail_raw: string,
};
- pub const Template = struct { tag: ?ExprNodeIndex, head: JavascriptString, head_raw: string, // This is only filled out for tagged template literals
- parts: ?[]TemplatePart, legacy_octal_loc: logger.Loc };
+ pub const Template = struct { tag: ?ExprNodeIndex = null, head: JavascriptString, head_raw: string, // This is only filled out for tagged template literals
+ parts: ?[]TemplatePart = null, legacy_octal_loc: logger.Loc };
pub const RegExp = struct {
value: string,
@@ -667,8 +700,8 @@ pub const E = struct {
pub const Await = struct { value: ExprNodeIndex };
pub const Yield = struct {
- value: ?ExprNodeIndex,
- is_star: bool,
+ value: ?ExprNodeIndex = null,
+ is_star: bool = false,
};
pub const If = struct {
@@ -963,7 +996,146 @@ pub const Expr = struct {
pub const Flags = enum { none, ts_decorator };
- pub const Data = union(enum) {
+ pub fn init(data: anytype, loc: logger.Loc) Expr {
+ switch (@TypeOf(data)) {
+ E.Array => {
+ return Expr{ .loc = loc, .data = Data{ .e_array = data } };
+ },
+ E.Unary => {
+ return Expr{ .loc = loc, .data = Data{ .e_unary = data } };
+ },
+ E.Binary => {
+ return Expr{ .loc = loc, .data = Data{ .e_binary = data } };
+ },
+ E.Boolean => {
+ return Expr{ .loc = loc, .data = Data{ .e_boolean = data } };
+ },
+ E.Super => {
+ return Expr{ .loc = loc, .data = Data{ .e_super = data } };
+ },
+ E.Null => {
+ return Expr{ .loc = loc, .data = Data{ .e_null = data } };
+ },
+ E.Undefined => {
+ return Expr{ .loc = loc, .data = Data{ .e_undefined = data } };
+ },
+ E.New => {
+ return Expr{ .loc = loc, .data = Data{ .e_new = data } };
+ },
+ E.NewTarget => {
+ return Expr{ .loc = loc, .data = Data{ .e_new_target = data } };
+ },
+ E.ImportMeta => {
+ return Expr{ .loc = loc, .data = Data{ .e_import_meta = data } };
+ },
+ E.Call => {
+ return Expr{ .loc = loc, .data = Data{ .e_call = data } };
+ },
+ E.Dot => {
+ return Expr{ .loc = loc, .data = Data{ .e_dot = data } };
+ },
+ E.Index => {
+ return Expr{ .loc = loc, .data = Data{ .e_index = data } };
+ },
+ E.Arrow => {
+ return Expr{ .loc = loc, .data = Data{ .e_arrow = data } };
+ },
+ E.Identifier => {
+ return Expr{ .loc = loc, .data = Data{ .e_identifier = data } };
+ },
+ E.ImportIdentifier => {
+ return Expr{ .loc = loc, .data = Data{ .e_import_identifier = data } };
+ },
+ E.PrivateIdentifier => {
+ return Expr{ .loc = loc, .data = Data{ .e_private_identifier = data } };
+ },
+ E.JSXElement => {
+ return Expr{ .loc = loc, .data = Data{ .e_jsx_element = data } };
+ },
+ E.Missing => {
+ return Expr{ .loc = loc, .data = Data{ .e_missing = data } };
+ },
+ E.Number => {
+ return Expr{ .loc = loc, .data = Data{ .e_number = data } };
+ },
+ E.BigInt => {
+ return Expr{ .loc = loc, .data = Data{ .e_big_int = data } };
+ },
+ E.Object => {
+ return Expr{ .loc = loc, .data = Data{ .e_object = data } };
+ },
+ E.Spread => {
+ return Expr{ .loc = loc, .data = Data{ .e_spread = data } };
+ },
+ E.String => {
+ return Expr{ .loc = loc, .data = Data{ .e_string = data } };
+ },
+ E.TemplatePart => {
+ return Expr{ .loc = loc, .data = Data{ .e_template_part = data } };
+ },
+ E.Template => {
+ return Expr{ .loc = loc, .data = Data{ .e_template = data } };
+ },
+ E.RegExp => {
+ return Expr{ .loc = loc, .data = Data{ .e_reg_exp = data } };
+ },
+ E.Await => {
+ return Expr{ .loc = loc, .data = Data{ .e_await = data } };
+ },
+ E.Yield => {
+ return Expr{ .loc = loc, .data = Data{ .e_yield = data } };
+ },
+ E.If => {
+ return Expr{ .loc = loc, .data = Data{ .e_if = data } };
+ },
+ E.RequireOrRequireResolve => {
+ return Expr{ .loc = loc, .data = Data{ .e_require_or_require_resolve = data } };
+ },
+ E.Import => {
+ return Expr{ .loc = loc, .data = Data{ .e_import = data } };
+ },
+ else => {
+ @compileError("Invalid type passed to Expr.init");
+ },
+ }
+ }
+
+ pub const Tag = enum {
+ e_array,
+ e_unary,
+ e_binary,
+ e_boolean,
+ e_super,
+ e_null,
+ e_undefined,
+ e_new,
+ e_new_target,
+ e_import_meta,
+ e_call,
+ e_dot,
+ e_index,
+ e_arrow,
+ e_identifier,
+ e_import_identifier,
+ e_private_identifier,
+ e_jsx_element,
+ e_missing,
+ e_number,
+ e_big_int,
+ e_object,
+ e_spread,
+ e_string,
+ e_template_part,
+ e_template,
+ e_reg_exp,
+ e_await,
+ e_yield,
+ e_if,
+ e_require_or_require_resolve,
+ e_import,
+ };
+
+ pub const Data = union(Tag) {
e_array: E.Array,
e_unary: E.Unary,
e_binary: E.Binary,
@@ -1038,7 +1210,7 @@ pub const EnumValue = struct {
};
pub const S = struct {
- pub const Block = struct { stmts: []StmtNodeIndex };
+ pub const Block = struct { stmts: StmtNodeList };
pub const Comment = struct { text: string };
@@ -1073,7 +1245,7 @@ pub const S = struct {
pub const Namespace = struct {
name: LocRef,
arg: Ref,
- stmts: []StmtNodeIndex,
+ stmts: StmtNodeList,
is_export: bool,
};
@@ -1119,10 +1291,10 @@ pub const S = struct {
};
pub const Try = struct {
- body: []StmtNodeIndex,
+ body: StmtNodeList,
body_loc: logger.Log,
- catch_: ?Catch,
- finally: ?Finally,
+ catch_: ?Catch = null,
+ finally: ?Finally = null,
};
pub const Switch = struct {
@@ -1150,8 +1322,8 @@ pub const S = struct {
// when converting this module to a CommonJS module.
namespace_ref: Ref, default_name: *LocRef, items: *[]ClauseItem, star_name_loc: *logger.Loc, import_record_index: u32, is_single_line: bool };
- pub const Return = struct {};
- pub const Throw = struct {};
+ pub const Return = struct { value: ?ExprNodeIndex = null };
+ pub const Throw = struct { value: ExprNodeIndex };
pub const Local = struct {
kind: Kind = Kind.k_var,
@@ -1180,15 +1352,15 @@ pub const S = struct {
pub const Catch = struct {
loc: logger.Loc,
binding: ?BindingNodeIndex,
- body: []StmtNodeIndex,
+ body: StmtNodeList,
};
pub const Finally = struct {
loc: logger.Loc,
- stmts: []StmtNodeIndex,
+ stmts: StmtNodeList,
};
-pub const Case = struct { loc: logger.Loc, value: ?ExprNodeIndex, body: []StmtNodeIndex };
+pub const Case = struct { loc: logger.Loc, value: ?ExprNodeIndex, body: StmtNodeList };
pub const Op = struct {
// If you add a new token, remember to add it to "OpTable" too
@@ -1686,7 +1858,23 @@ pub const Scope = struct {
}
};
-// test "ast" {
-// const ast = Ast{};
-// }
+test "Binding.init" {
+ var binding = Binding.init(
+ B.Identifier{ .ref = Ref{ .source_index = 0, .inner_index = 10 } },
+ logger.Loc{ .start = 1 },
+ );
+ std.testing.expect(binding.loc.start == 1);
+ std.testing.expect(@as(Binding.Tag, binding.data) == Binding.Tag.b_identifier);
+}
+test "Expr.init" {
+ var ident = Expr.init(E.Identifier{}, logger.Loc{ .start = 100 });
+ var list = [_]Expr{ident};
+ var expr = Expr.init(
+ E.Array{ .items = list[0..] },
+ logger.Loc{ .start = 1 },
+ );
+ std.testing.expect(expr.loc.start == 1);
+ std.testing.expect(@as(Expr.Tag, expr.data) == Expr.Tag.e_array);
+ std.testing.expect(expr.data.e_array.items[0].loc.start == 100);
+}
diff --git a/src/js_lexer_tables.zig b/src/js_lexer_tables.zig
index ab6b0f95a..0ab696b62 100644
--- a/src/js_lexer_tables.zig
+++ b/src/js_lexer_tables.zig
@@ -178,6 +178,18 @@ pub const Keywords = std.ComptimeStringMap(T, .{
.{ "with", .t_with },
});
+pub const StrictModeReservedWords = std.ComptimeStringMap(Bool, .{
+ .{ "implements", true },
+ .{ "interface", true },
+ .{ "let", true },
+ .{ "package", true },
+ .{ "private", true },
+ .{ "protected", true },
+ .{ "public", true },
+ .{ "static", true },
+ .{ "yield", true },
+});
+
pub const CodePoint = i22;
pub const TokenEnumType = std.EnumArray(T, []u8);
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 5f83facd9..d48a7b0eb 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -24,6 +24,7 @@ const Binding = js_ast.Binding;
const Symbol = js_ast.Symbol;
const Level = js_ast.Op.Level;
const Op = js_ast.Op;
+const Scope = js_ast.Scope;
const locModuleScope = logger.Loc.Empty;
const s = Stmt.init;
@@ -37,6 +38,10 @@ fn lexerpanic() noreturn {
std.debug.panic("LexerPanic", .{});
}
+fn fail() noreturn {
+ std.debug.panic("Something went wrong :cry;", .{});
+}
+
const TempRef = struct {
ref: js_ast.Ref,
value: *js_ast.Expr,
@@ -53,6 +58,26 @@ const ThenCatchChain = struct {
has_catch: bool = false,
};
+const StrictModeFeature = enum {
+ with_statement,
+ delete_bare_name,
+ for_in_var_init,
+ eval_or_arguments,
+ reserved_word,
+ legacy_octal_literal,
+ legacy_octal_escape,
+ if_else_function_stmt,
+};
+
+const SymbolMergeResult = enum {
+ forbidden,
+ replace_with_new,
+ overwrite_with_new,
+ keep_existing,
+ become_private_get_set_pair,
+ become_private_static_get_set_pair,
+};
+
const Map = std.AutoHashMap;
const List = std.ArrayList;
@@ -172,6 +197,12 @@ const DeferredErrors = struct {
}
};
+const ParenExprOpts = struct {
+ async_range: ?logger.Range = null,
+ is_async: bool = false,
+ force_arrow_fn: bool = false,
+};
+
const ModuleType = enum { esm };
const PropertyOpts = struct {
@@ -563,6 +594,80 @@ const P = struct {
}
}
+ pub fn canMergeSymbols(p: *P, scope: *js_ast.Scope, existing: Symbol.Kind, new: Symbol.Kind) SymbolMergeResult {
+ if (existing == .unbound) {
+ return .replace_with_new;
+ }
+
+ // In TypeScript, imports are allowed to silently collide with symbols within
+ // the module. Presumably this is because the imports may be type-only:
+ //
+ // import {Foo} from 'bar'
+ // class Foo {}
+ //
+ if (p.options.ts and existing == .import) {
+ return .replace_with_new;
+ }
+
+ // "enum Foo {} enum Foo {}"
+ // "namespace Foo { ... } enum Foo {}"
+ if (new == .ts_enum and (existing == .ts_enum or existing == .ts_namespace)) {
+ return .replace_with_new;
+ }
+
+ // "namespace Foo { ... } namespace Foo { ... }"
+ // "function Foo() {} namespace Foo { ... }"
+ // "enum Foo {} namespace Foo { ... }"
+ if (new == .ts_namespace) {
+ switch (existing) {
+ .ts_namespace, .hoisted_function, .generator_or_async_function, .ts_enum, .class => {
+ return .keep_existing;
+ },
+ else => {},
+ }
+ }
+
+ // "var foo; var foo;"
+ // "var foo; function foo() {}"
+ // "function foo() {} var foo;"
+ // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }"
+ if (Symbol.isKindHoistedOrFunction(new) and Symbol.isKindHoistedOrFunction(existing) and (scope.kind == .entry or scope.kind == .function_body ||
+ (Symbol.isKindHoisted(new) and Symbol.isKindHoisted(existing))))
+ {
+ return .keep_existing;
+ }
+
+ // "get #foo() {} set #foo() {}"
+ // "set #foo() {} get #foo() {}"
+ if ((existing == .private_get and new == .private_set) or
+ (existing == .private_set and new == .private_get))
+ {
+ return .become_private_get_set_pair;
+ }
+ if ((existing == .private_static_get and new == .private_static_set) or
+ (existing == .private_static_set and new == .private_static_get))
+ {
+ return .become_private_static_get_set_pair;
+ }
+
+ // "try {} catch (e) { var e }"
+ if (existing == .catch_identifier and new == .hoisted) {
+ return .replace_with_new;
+ }
+
+ // "function() { var arguments }"
+ if (existing == .arguments and new == .hoisted) {
+ return .keep_existing;
+ }
+
+ // "function() { let arguments }"
+ if (existing == .arguments and new != .hoisted) {
+ return .overwrite_with_new;
+ }
+
+ return .forbidden;
+ }
+
pub fn prepareForVisitPass(p: *P) !void {
try p.pushScopeForVisitPass(js_ast.Scope.Kind.entry, locModuleScope);
p.fn_or_arrow_data_visit.is_outside_fn_or_arrow = true;
@@ -1025,7 +1130,8 @@ const P = struct {
}
defaultName = try createDefaultName(p, loc);
- var expr = p.parseSuffix(p.parseAsyncPrefixExpr(async_range, Level.comma), Level.comma, null, Level.lowest);
+ // TODO: here
+ var expr = try p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, Level.comma), Level.comma, null, Expr.Flags.none);
p.lexer.expectOrInsertSemicolon();
// this is probably a panic
var value = js_ast.StmtOrExpr{ .expr = &expr };
@@ -1074,20 +1180,417 @@ const P = struct {
return stmts.toOwnedSlice();
}
+ pub fn markStrictModeFeature(p: *P, feature: StrictModeFeature, r: logger.Range, detail: string) !void {
+ var text: string = undefined;
+ var can_be_transformed = false;
+ switch (feature) {
+ .with_statement => {
+ text = "With statements";
+ },
+ .delete_bare_name => {
+ text = "\"delete\" of a bare identifier";
+ },
+ .for_in_var_init => {
+ text = "Variable initializers within for-in loops";
+ can_be_transformed = true;
+ },
+ .eval_or_arguments => {
+ text = try std.fmt.allocPrint(p.allocator, "Declarations with the name {s}", .{detail});
+ },
+ .reserved_word => {
+ text = try std.fmt.allocPrint(p.allocator, "{s} is a reserved word and", .{detail});
+ },
+ .legacy_octal_escape => {
+ text = "Legacy octal literals";
+ },
+ .legacy_octal_escape => {
+ text = "Legacy octal escape sequences";
+ },
+ .if_else_function_stmt => {
+ text = "Function declarations inside if statements";
+ },
+ else => {
+ text = "This feature";
+ },
+ }
+
+ if (p.current_scope) |scope| {
+ if (p.isStrictMode()) {
+ var why: string = "";
+ var notes: []logger.MsgData = undefined;
+ var where: logger.Range = undefined;
+ switch (scope.strict_mode) {
+ .implicit_strict_mode_import => {
+ where = p.es6_import_keyword;
+ },
+ .implicit_strict_mode_export => {
+ where = p.es6_export_keyword;
+ },
+ .implicit_strict_mode_top_level_await => {
+ where = p.top_level_await_keyword;
+ },
+ .implicit_strict_mode_class => {
+ why = "All code inside a class is implicitly in strict mode";
+ where = p.enclosing_class_keyword;
+ },
+ }
+ if (why.len == 0) {
+ why = try std.fmt.allocPrint(p.allocator, "This file is implicitly in strict mode because of the \"{s}\" keyword here", p.source.textForRange(where));
+ }
+
+ p.log.addRangeErrorWithNotes(p.source, r, std.fmt.allocPrint(p.allocator, "{s} cannot be used in strict mode", .{text}), []logger.Data{logger.rangeData(p.source, where, why)});
+ } else if (!can_be_transformed and p.isStrictModeOutputFormat()) {
+ p.log.addRangeError(p.source, r, std.fmt.allocPrint(p.allocator, "{s} cannot be used with \"esm\" due to strict mode", .{text}));
+ }
+ }
+ }
+
+ pub fn isStrictMode(p: *P) bool {
+ return p.current_scope.?.strict_mode != .sloppy_mode;
+ }
+
+ pub fn isStrictModeOutputFormat(p: *P) bool {
+ return true;
+ }
+
+ pub fn declareSymbol(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string) !Ref {
+ // p.checkForNonBMPCodePoint(loc, name)
+
+ // Forbid declaring a symbol with a reserved word in strict mode
+ if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) {
+ p.markStrictModeFeature(reservedWord, js_lexer.rangeOfIdentifier(p.source, loc), name);
+ }
+
+ // Allocate a new symbol
+ var ref = p.newSymbol(kind, name);
+
+ const scope = p.current_scope orelse unreachable;
+ if (scope.members.get(name)) |existing| {
+ const symbol: Symbol = p.symbols.items[@intCast(usize, existing.ref.inner_index)];
+
+ switch (p.canMergeSymbols(p.current_scope, symbol.kind, kind)) {
+ .forbidden => {
+ const r = js_lexer.rangeOfIdentifier(p.source.*, loc);
+ var notes: []logger.Data = undefined;
+ notes = []logger.Data{logger.rangeData(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} has already been declared", .{name}))};
+ p.log.addRangeErrorWithNotes(
+ p.source,
+ r,
+ try std.fmt.allocPrint(p.allocator, "{s} was originally declared here", .{name}),
+ );
+ return existing.ref;
+ },
+ .keep_existing => {
+ ref = existing.ref;
+ },
+ .replace_with_new => {
+ symbol.link = ref;
+ },
+ .become_private_get_set_pair => {
+ ref = existing.ref;
+ symbol.kind = .private_get_set_pair;
+ },
+ .become_private_static_get_set_pair => {
+ ref = existing.ref;
+ symbol.kind = .private_static_get_set_pair;
+ },
+ .new => {},
+ else => unreachable,
+ }
+ }
+
+ try scope.members.put(name, js_ast.Scope.Member{ .ref = ref, .loc = loc });
+ return ref;
+ }
+
+ pub fn parseFnExpr(p: *P, loc: logger.Loc, is_async: bool, async_range: logger.Range) !ExprNodeIndex {
+ p.lexer.next();
+ const is_generator = p.lexer.token == T.t_asterisk;
+ if (is_generator) {
+ // p.markSyntaxFeature()
+ p.lexer.next();
+ } else if (is_async) {
+ // p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator)
+ }
+
+ var name: ?js_ast.LocRef = null;
+
+ p.pushScopeForParsePass(.function_args, loc);
+ defer p.popScope();
+
+ if (p.lexer.token == .t_identifier) {
+ name = p._(js_ast.LocRef{
+ .loc = loc,
+ .ref = null,
+ });
+
+ if (p.lexer.identifier.len > 0 and !strings.eql(p.lexer.identifier, "arguments")) {
+ name.ref = try p.declareSymbol(.hoisted_function, name.loc, p.lexer.identifier);
+ } else {
+ name.ref = try p.newSymbol(.hoisted_function, p.lexer.identifier);
+ }
+ p.lexer.next();
+ }
+
+ if (p.options.ts) {
+ p.skipTypescriptTypeParameters();
+ }
+
+ var func = p.parseFn(name, FnOrDDataParse{
+ .async_range = async_range,
+ .allow_await = is_async,
+ .allow_yield = is_generator,
+ });
+
+ return _(Expr.init(js_ast.E.Function{
+ .func = func,
+ }, loc));
+ }
+
+ pub fn parseFnBody(p: *P, data: FnOrArrowDataParse) !G.FnBody {
+ var oldFnOrArrowData = p.fn_or_arrow_data_parse;
+ var oldAllowIn = p.allow_in;
+ p.fn_or_arrow_data_parse = data;
+ p.allow_in = true;
+
+ p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc());
+ defer p.popScope();
+
+ p.lexer.expect(.t_open_brace);
+ const stmts = try p.parseStmtsUpTo(.t_close_brace, ParseStatementOptions{});
+ p.lexer.next();
+
+ p.allow_in = oldAllowIn;
+ p.fn_or_arrow_data_parse = oldFnOrArrowData;
+ return G.FnBody{ .loc = loc, .stmts = stmts };
+ }
+
+ pub fn parseArrowBody(p: *P, args: []js_ast.G.Arg, data: FnOrArrowDataParse) !E.Arrow {
+ var arrow_loc = p.lexer.loc();
+
+ // Newlines are not allowed before "=>"
+ if (p.lexer.has_newline_before) {
+ try p.log.addRangeError(p.source, p.lexer.range(), "Unexpected newline before \"=>\"");
+ fail();
+ }
+
+ p.lexer.expect(T.t_equals_greater_than);
+
+ for (args) |arg| {
+ try p.declareBinding(Symbol.Kind.hoisted, arg.binding, ParseStatementOptions{});
+ }
+
+ data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call;
+ if (p.lexer.token == .t_open_brace) {
+ var body = p.parseFnBody(data);
+ p.after_arrow_body_loc = p.lexer.loc();
+ return p._(E.Arrow{ .args = args, .body = body });
+ }
+
+ p.pushScopeForParsePass(Scope.Kind.function_body, arrow_loc);
+ defer p.popScope();
+
+ var old_fn_or_arrow_data = p.fn_or_arrow_data_parse;
+ p.fn_or_arrow_data_parse = data;
+ var expr = try p.parseExpr(Level.comma);
+ p.fn_or_arrow_data_parse = old_fn_or_arrow_data;
+ return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = []StmtNodeIndex{p._(Stmt.init(S.Return{ .value = expr }, arrow_loc))} } };
+ }
+
+ pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: ParseStatementOptions) !void {
+ switch (binding.data) {
+ .b_identifier, .b_missing => |b| {
+ if (!opts.is_typescript_declare || (opts.is_namespace_scope and opts.is_export)) {
+ b.ref = p.declareSymbol(kind, binding.loc, p.loadNameFromRef(b.ref));
+ }
+ },
+
+ .b_array => |b| {
+ for (b.items) |item| {
+ p.declareBinding(kind, item.binding, opts);
+ }
+ },
+
+ .b_object => |b| {
+ for (b.properties) |prop| {
+ p.declareBinding(kind, prop.value, opts);
+ }
+ },
+
+ else => {
+ @compileError("Missing binding type");
+ },
+ }
+ }
+
+ // Saves us from allocating a slice to the heap
+ pub fn parseArrowBodySingleArg(p: *P, arg: G.Arg, data: FnOrArrowDataParse) !E.Arrow {
+ var args: []G.Arg = []G.Arg{arg};
+ return p.parseArrowBody(args[0..], data);
+ }
+
+ // This is where the allocate memory to the heap for AST objects.
+ // This is a short name to keep the code more readable.
+ // It also swallows errors, but I think that's correct here.
+ // We can handle errors via the log.
+ // We'll have to deal with @wasmHeapGrow or whatever that thing is.
+ pub fn __(self: *P, comptime ast_object_type: type, instance: anytype) callconv(.Inline) *ast_object_type {
+ var obj = self.allocator.create(ast_object_type) catch unreachable;
+ obj = instance;
+ return obj;
+ }
+ pub fn _(self: *P, kind: anytype) callconv(.Inline) *@TypeOf(kind) {
+ return self.__(@TypeOf(kind), kind);
+ }
+
+ // The name is temporarily stored in the ref until the scope traversal pass
+ // happens, at which point a symbol will be generated and the ref will point
+ // to the symbol instead.
+ //
+ // The scope traversal pass will reconstruct the name using one of two methods.
+ // In the common case, the name is a slice of the file itself. In that case we
+ // can just store the slice and not need to allocate any extra memory. In the
+ // rare case, the name is an externally-allocated string. In that case we store
+ // an index to the string and use that index during the scope traversal pass.
+ pub fn storeNameInRef(p: *P, name: string) !js_ast.Ref {
+ // jarred: honestly, this is kind of magic to me
+ // but I think I think I understand it.
+ // the strings are slices.
+ // "name" is just a different place in p.source.contents's buffer
+ // Instead of copying a shit ton of strings everywhere
+ // we can just say "yeah this is really over here at inner_index"
+ // .source_index being null is used to identify was this allocated or is just in the orignial thing.
+ // you could never do this in JavaScript!!
+ const ptr0 = @ptrToInt(name);
+ const ptr1 = @ptrToInt(p.source.contents);
+
+ // Is the data in "name" a subset of the data in "p.source.Contents"?
+ if (ptr0 >= ptr1 and ptr0 + name.len < p.source.contents.len) {
+ std.debug.print("storeNameInRef fast path");
+ // The name is a slice of the file contents, so we can just reference it by
+ // length and don't have to allocate anything. This is the common case.
+ //
+ // It's stored as a negative value so we'll crash if we try to use it. That
+ // way we'll catch cases where we've forgotten to call loadNameFromRef().
+ // The length is the negative part because we know it's non-zero.
+ return js_ast.Ref{ .source_index = ptr1, .inner_index = (name.len + ptr0) };
+ } else {
+ std.debug.print("storeNameInRef slow path");
+ // The name is some memory allocated elsewhere. This is either an inline
+ // string constant in the parser or an identifier with escape sequences
+ // in the source code, which is very unusual. Stash it away for later.
+ // This uses allocations but it should hopefully be very uncommon.
+
+ // allocated_names is lazily allocated
+ if (p.allocated_names.capacity > 0) {
+ const inner_index = p.allocated_names.items.len;
+ try p.allocated_names.append(name);
+ return js_ast.Ref{ .source_index = 0x80000000, .inner_index = inner_index };
+ } else {
+ try p.allocated_names.initCapacity(p.allocator, 1);
+ p.allocated_names.appendAssumeCapacity(name);
+ return js_ast.Ref{ .source_index = 0x80000000, .inner_index = 0 };
+ }
+
+ // p.allocatedNames = append(p.allocatedNames, name)
+ // return ref
+ }
+ }
+
+ pub fn loadNameFromRef(p: *P, ref: js_ast.Ref) string {
+ if (ref.source_index == 0x80000000) {
+ return (p.allocated_names orelse unreachable)[ref.inner_index];
+ } else if (ref.source_index) |source_index| {
+ if (std.builtin.mode != std.builtin.Mode.ReleaseFast) {
+ std.debug.assert(ref.inner_index - source_index > 0);
+ }
+
+ return p.source.contents[ref.inner_index .. ref.inner_index - source_index];
+ } else {
+ std.debug.panic("Internal error: invalid symbol reference.", .{ref});
+ return p.source.contents[ref.inner_index .. ref.inner_index - source_index];
+ }
+ }
+
// This parses an expression. This assumes we've already parsed the "async"
// keyword and are currently looking at the following token.
- pub fn parseAsyncPrefixExpr(p: *P, async_range: logger.Range, level: Level) Expr {
+ pub fn parseAsyncPrefixExpr(p: *P, async_range: logger.Range, level: Level) !Expr {
// "async function() {}"
if (!p.lexer.has_newline_before and p.lexer.token == T.t_function) {
- return p.parseFnExpr(async_range.loc, true, async_range);
+ return try p.parseFnExpr(async_range.loc, true, async_range);
}
// Check the precedence level to avoid parsing an arrow function in
// "new async () => {}". This also avoids parsing "new async()" as
// "new (async())()" instead.
- if (!p.lexer.has_newline_before and level < Level.member) {}
+ if (!p.lexer.has_newline_before and level < Level.member) {
+ switch (p.lexer.token) {
+ // "async => {}"
+ .t_equals_greater_than => {
+ const arg = G.Arg{ .binding = p._(Binding.init(
+ B.Identifier{
+ .ref = p.storeNameInRef("async"),
+ },
+ async_range.loc,
+ )) };
+ p.pushScopeForParsePass(.function_args, async_range.loc);
+ defer p.popScope();
+ var arrowBody = try p.parseArrowBodySingleArg(arg, FnOrArrowDataParse{});
+ return Expr.init(arrowBody, async_range.loc);
+ },
+ // "async x => {}"
+ .t_identifier => {
+ // p.markLoweredSyntaxFeature();
+ const ref = p.storeNameInRef(p.lexer.identifier);
+ var arg = G.Arg{ .binding = p._(Binding.init(B.Identifier{
+ .ref = ref,
+ }, p.lexer.loc())) };
+ p.lexer.next();
+
+ p.pushScopeForParsePass(.function_args, async_range.loc);
+ defer p.popScope();
+
+ var arrowBody = try p.parseArrowBodySingleArg(arg, FnOrArrowDataParse{
+ .allow_await = true,
+ });
+ arrowBody.is_async = true;
+ return Expr.init(arrowBody, async_range.loc);
+ },
+
+ // "async()"
+ // "async () => {}"
+ .t_open_paren => {
+ p.lexer.next();
+ return p.parseParenExpr(async_range.loc, ParenExprOptions{ .is_async = true, .async_range = asyncRange });
+ },
+
+ // "async<T>()"
+ // "async <T>() => {}"
+ .t_less_than => {
+ if (p.options.ts and p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking) {
+ p.lexer.next();
+ return p.parseParenExpr(async_range.loc, ParenExprOptions{ .is_async = true, .async_range = asyncRange });
+ }
+ },
+
+ else => {},
+ }
+ }
+
+ // "async"
+ // "async + 1"
+ return Expr.init(
+ E.Identifier{ .ref = p.storeNameInRef("async") },
+ async_range.loc,
+ );
}
+ pub fn trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() void {
+ notimpl();
+ }
+
+ pub fn parseParenExpr(p: *P, loc: logger.Loc, opts: ParenExprOpts) !Expr {}
+
pub fn popScope(p: *P) void {
const current_scope = p.current_scope orelse unreachable;
// We cannot rename anything inside a scope containing a direct eval() call
diff --git a/src/logger.zig b/src/logger.zig
index 70aaa4ebf..629e146dc 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -285,7 +285,7 @@ pub const Source = struct {
}
};
-fn rangeData(source: ?Source, r: Range, text: string) Data {
+pub fn rangeData(source: ?Source, r: Range, text: string) Data {
return Data{ .text = text, .location = Location.init_or_nil(source, r) };
}