diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/js_ast.zig | 2 | ||||
| -rw-r--r-- | src/js_parser.zig | 331 |
2 files changed, 327 insertions, 6 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index 6054c0ae8..cdf06f2b2 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2488,7 +2488,7 @@ pub const Op = struct { pub const ArrayBinding = struct { binding: BindingNodeIndex, - default_value: ?ExprNodeIndex, + default_value: ?ExprNodeIndex = null, }; pub const Ast = struct { diff --git a/src/js_parser.zig b/src/js_parser.zig index 4e2e4e6ad..8ac4eb238 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1598,13 +1598,38 @@ const P = struct { notimpl(); }, .t_class => { - notimpl(); + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(loc); + } + + return p.parseClassStmt(loc, opts); }, .t_var => { - notimpl(); + p.lexer.next(); + const decls = p.parseAndDeclareDecls(.hoisted, opts); + p.lexer.expectOrInsertSemicolon(); + return p.s(S.Local{ .kind = .k_var, .decls = decls, .is_export = opts.is_export }, loc); }, .t_const => { - notimpl(); + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(loc); + } + // p.markSyntaxFeature(compat.Const, p.lexer.Range()) + + p.lexer.next(); + + if (p.options.ts and p.lexer.token == T.t_enum) { + return p.parseTypescriptEnumStmt(loc, opts); + } + + const decls = p.parseAndDeclareDecls(.cconst, opts); + p.lexer.expectOrInsertSemicolon(); + + if (!opts.is_typescript_declare) { + try p.requireInitializers(decls); + } + + return p.s(S.Local{ .kind = .k_const, .decls = decls, .is_export = opts.is_export }, loc); }, .t_if => { notimpl(); @@ -1657,6 +1682,301 @@ const P = struct { return js_ast.Stmt.empty(); } + pub fn requireInitializers(p: *P, decls: []G.Decl) !void { + for (decls) |decl| { + if (decl.value) |val| { + switch (decl.binding.data) { + .b_identifier => |ident| { + const r = js_lexer.rangeOfIdentifier(&p.source, decl.binding.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "The constant \"{s}\" must be initialized", .{p.symbols.items[ident.ref.inner_index].original_name}); + return; + }, + else => {}, + } + } + + try p.log.addError(p.source, decl.binding.loc, "This constant must be initialized"); + } + } + + pub fn parseBinding(p: *P) Binding { + var loc = p.lexer.loc(); + + switch (p.lexer.token) { + .t_identifier => { + const name = p.lexer.identifier; + if ((p.fn_or_arrow_data_parse.allow_await and strings.eql(name, "await")) or (p.fn_or_arrow_data_parse.allow_yield and strings.eql(name, "yield"))) { + // TODO: add fmt to addRangeError + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"yield\" or \"await\" here.") catch unreachable; + } + + const ref = p.storeNameInRef(name) catch unreachable; + p.lexer.next(); + return p.b(B.Identifier{ .ref = ref }, loc); + }, + .t_open_bracket => { + p.lexer.next(); + var is_single_line = !p.lexer.has_newline_before; + var items = List(js_ast.ArrayBinding).init(p.allocator); + var has_spread = false; + + // "in" expressions are allowed + var old_allow_in = p.allow_in; + p.allow_in = true; + while (p.lexer.token != .t_close_bracket) { + if (p.lexer.token == .t_comma) { + items.append(js_ast.ArrayBinding{ + .binding = p.b( + B.Missing{}, + p.lexer.loc(), + ), + }) catch unreachable; + } else { + if (p.lexer.token == .t_dot_dot_dot) { + p.lexer.next(); + has_spread = true; + + // This was a bug in the ES2015 spec that was fixed in ES2016 + if (p.lexer.token != .t_identifier) { + // p.markSyntaxFeature(compat.NestedRestBinding, p.lexer.Range()) + + } + } + + const binding = p.parseBinding(); + + var default_value: ?Expr = null; + if (!has_spread and p.lexer.token == .t_equals) { + p.lexer.next(); + default_value = p.parseExpr(.comma); + } + + items.append(js_ast.ArrayBinding{ .binding = binding, .default_value = default_value }) catch unreachable; + + // Commas after spread elements are not allowed + if (has_spread and p.lexer.token == .t_comma) { + p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; + fail(); + } + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.next(); + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + p.allow_in = old_allow_in; + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.expect(.t_close_bracket); + return p.b(B.Array{ + .items = items.toOwnedSlice(), + .has_spread = has_spread, + .is_single_line = is_single_line, + }, loc); + }, + .t_open_brace => { + // p.markSyntaxFeature(compat.Destructuring, p.lexer.Range()) + p.lexer.next(); + var is_single_line = false; + var properties = List(js_ast.B.Property).init(p.allocator); + + // "in" expressions are allowed + var old_allow_in = p.allow_in; + p.allow_in = true; + + while (p.lexer.token != .t_close_brace) { + var property = p.parsePropertyBinding(); + properties.append(property) catch unreachable; + + // Commas after spread elements are not allowed + if (property.flags.is_spread and p.lexer.token == .t_comma) { + p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; + fail(); + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.next(); + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + p.allow_in = old_allow_in; + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.expect(.t_close_brace); + + return p.b(B.Object{ + .properties = properties.toOwnedSlice(), + .is_single_line = is_single_line, + }, loc); + }, + else => {}, + } + + p.lexer.expect(.t_identifier); + return p.b(B.Missing{}, loc); + } + + pub fn parsePropertyBinding(p: *P) B.Property { + var key: js_ast.Expr = undefined; + var is_computed = false; + + switch (p.lexer.token) { + .t_dot_dot_dot => { + p.lexer.next(); + const value = p.b(B.Identifier{ + .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable, + }, p.lexer.loc()); + p.lexer.expect(.t_identifier); + return B.Property{ + // This "key" diverges from esbuild, but is due to Go always having a zero value. + .key = p.e(E.Missing{}, logger.Loc.Empty), + + .flags = Flags.Property{ .is_spread = true }, + .value = value, + }; + }, + .t_numeric_literal => { + key = p.e(E.Number{ + .value = p.lexer.number, + }, p.lexer.loc()); + // check for legacy octal literal + p.lexer.next(); + }, + .t_string_literal => { + key = p.parseStringLiteral(); + }, + .t_big_integer_literal => { + key = p.e(E.BigInt{ + .value = p.lexer.identifier, + }, p.lexer.loc()); + // p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) + p.lexer.next(); + }, + .t_open_bracket => { + is_computed = true; + p.lexer.next(); + key = p.parseExpr(.comma); + p.lexer.expect(.t_close_bracket); + }, + else => { + const name = p.lexer.identifier; + const loc = p.lexer.loc(); + + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + + p.lexer.next(); + + key = p.e(E.String{ + .value = p.lexer.stringToUTF16(name), + }, loc); + + if (p.lexer.token != .t_colon and p.lexer.token != .t_open_paren) { + const ref = p.storeNameInRef(name) catch unreachable; + const value = p.b(B.Identifier{ .ref = ref }, loc); + var default_value: ?Expr = null; + if (p.lexer.token == .t_equals) { + p.lexer.next(); + default_value = p.parseExpr(.comma); + } + + return B.Property{ + .key = key, + .value = value, + .default_value = default_value, + }; + } + }, + } + + p.lexer.expect(.t_colon); + const value = p.parseBinding(); + + var default_value: ?Expr = null; + if (p.lexer.token == .t_equals) { + p.lexer.next(); + default_value = p.parseExpr(.comma); + } + + return B.Property{ + .flags = Flags.Property{ + .is_computed = is_computed, + }, + .key = key, + .value = value, + .default_value = default_value, + }; + } + + pub fn parseAndDeclareDecls(p: *P, kind: Symbol.Kind, opts: *ParseStatementOptions) []G.Decl { + var decls = List(G.Decl).initCapacity(p.allocator, 1) catch unreachable; + + while (true) { + // Forbid "let let" and "const let" but not "var let" + if ((kind == .other or kind == .cconst) and p.lexer.isContextualKeyword("let")) { + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"let\" as an identifier here") catch unreachable; + } + + var value: ?js_ast.Expr = null; + var local = p.parseBinding(); + p.declareBinding(kind, local, opts) catch unreachable; + + // Skip over types + if (p.options.ts) { + // "let foo!" + var is_definite_assignment_assertion = p.lexer.token == .t_exclamation; + if (is_definite_assignment_assertion) { + p.lexer.next(); + } + + // "let foo: number" + if (is_definite_assignment_assertion or p.lexer.token == .t_colon) { + p.lexer.expect(.t_colon); + p.skipTypescriptType(.lowest); + } + } + + if (p.lexer.token == .t_equals) { + p.lexer.next(); + value = p.parseExpr(.comma); + } + + decls.append(G.Decl{ + .binding = local, + .value = value, + }) catch unreachable; + + if (p.lexer.token != .t_comma) { + break; + } + p.lexer.next(); + } + + return decls.toOwnedSlice(); + } + pub fn parseTypescriptEnumStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) Stmt { notimpl(); // return Stmt.empty(); @@ -1995,7 +2315,8 @@ const P = struct { p.lexer.expect(T.t_equals_greater_than); for (args) |arg| { - try p.declareBinding(Symbol.Kind.hoisted, arg.binding, ParseStatementOptions{}); + var opts = ParseStatementOptions{}; + try p.declareBinding(Symbol.Kind.hoisted, arg.binding, &opts); } data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call; @@ -2019,7 +2340,7 @@ const P = struct { return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } }; } - pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: ParseStatementOptions) !void { + pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: *ParseStatementOptions) !void { switch (binding.data) { .b_identifier => |bind| { if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) { |
