diff options
-rw-r--r-- | .vscode/launch.json | 21 | ||||
-rw-r--r-- | src/fs.zig | 1 | ||||
-rw-r--r-- | src/js_ast.zig | 300 | ||||
-rw-r--r-- | src/js_lexer_tables.zig | 12 | ||||
-rw-r--r-- | src/js_parser.zig | 511 | ||||
-rw-r--r-- | src/logger.zig | 2 |
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) }; } |