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.zig264
1 files changed, 245 insertions, 19 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig
index a5dc2f36b..5f83facd9 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -21,12 +21,22 @@ const E = js_ast.E;
const Stmt = js_ast.Stmt;
const Expr = js_ast.Expr;
const Binding = js_ast.Binding;
+const Symbol = js_ast.Symbol;
+const Level = js_ast.Op.Level;
+const Op = js_ast.Op;
const locModuleScope = logger.Loc.Empty;
-fn notimpl() void {
+const s = Stmt.init;
+const e = Expr.init;
+
+fn notimpl() noreturn {
std.debug.panic("Not implemented yet!!", .{});
}
+fn lexerpanic() noreturn {
+ std.debug.panic("LexerPanic", .{});
+}
+
const TempRef = struct {
ref: js_ast.Ref,
value: *js_ast.Expr,
@@ -46,6 +56,7 @@ const ThenCatchChain = struct {
const Map = std.AutoHashMap;
const List = std.ArrayList;
+const StmtList = List(Stmt);
const SymbolUseMap = Map(js_ast.Ref, js_ast.Symbol.Use);
const StringRefMap = std.StringHashMap(js_ast.Ref);
@@ -135,6 +146,32 @@ const FnOnlyDataVisit = struct {
is_this_nested: bool = false,
};
+// Due to ES6 destructuring patterns, there are many cases where it's
+// impossible to distinguish between an array or object literal and a
+// destructuring assignment until we hit the "=" operator later on.
+// This object defers errors about being in one state or the other
+// until we discover which state we're in.
+const DeferredErrors = struct {
+ // These are errors for expressions
+ invalid_expr_default_value: ?logger.Range = null,
+ invalid_expr_after_question: ?logger.Range = null,
+ array_spread_feature: ?logger.Range = null,
+
+ pub fn mergeInto(self: *DeferredErrors, to: *DeferredErrors) void {
+ if (self.invalid_expr_default_value) |inv| {
+ to.invalid_expr_default_value = inv;
+ }
+
+ if (self.invalid_expr_after_question) |inv| {
+ to.invalid_expr_after_question = inv;
+ }
+
+ if (self.array_spread_feature) |inv| {
+ to.array_spread_feature = inv;
+ }
+ }
+};
+
const ModuleType = enum { esm };
const PropertyOpts = struct {
@@ -630,7 +667,21 @@ const P = struct {
try p.log.addRangeError(p.source, p.lexer.range(), "Cannot use a declaration in a single-statement context");
}
- pub fn parseFnStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, asyncRange: ?logger.Range) !NodeIndex {
+ pub fn logExprErrors(p: *P, errors: *DeferredErrors) void {
+ if (errors.invalid_expr_default_value) |r| {
+ p.log.addRangeError(p.source, err, "Unexpected \"=\"", .{});
+ }
+
+ if (errors.invalid_expr_after_question) |r| {
+ p.log.addRangeError(p.source, r, "Unexpected %q", .{p.source.contents[r.loc.start..r.loc.end()]});
+ }
+
+ // if (errors.array_spread_feature) |err| {
+ // p.markSyntaxFeature(compat.ArraySpread, errors.arraySpreadFeature)
+ // }
+ }
+
+ pub fn parseFnStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, asyncRange: ?logger.Range) !Stmt {
const isGenerator = p.lexer.token == T.t_asterisk;
const isAsync = asyncRange != null;
@@ -696,7 +747,11 @@ const P = struct {
if (opts.is_typescript_declare or func.body == null) {
p.popAndDiscardScope(scopeIndex);
}
- return 0;
+
+ return Stmt.init(S.Function{
+ .func = func,
+ .is_export = opts.is_export,
+ }, func.open_parens_loc);
}
pub fn popAndDiscardScope(p: *P, scope_index: usize) void {
@@ -782,28 +837,56 @@ const P = struct {
// TODO:
pub fn parseTypeScriptDecorators(p: *P) []ExprNodeIndex {
notimpl();
- return undefined;
}
// TODO:
pub fn skipTypescriptType(p: *P, level: js_ast.Op.Level) void {
notimpl();
- return undefined;
}
// TODO:
pub fn skipTypescriptTypeParameters(p: *P) void {
notimpl();
- return undefined;
}
- pub fn parseStmt(p: *P, opts: *ParseStatementOptions) !NodeIndex {
+ fn createDefaultName(p: *P, loc: logger.Loc) !js_ast.LocRef {
+ var identifier = try std.fmt.allocPrint(p.allocator, "{s}_default", .{p.source.identifier_name});
+
+ const name = js_ast.LocRef{ .loc = loc, .ref = try p.newSymbol(Symbol.Kind.other, identifier) };
+
+ var scope = p.current_scope orelse unreachable;
+
+ try scope.generated.append(name.ref orelse unreachable);
+
+ return name;
+ }
+
+ pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !js_ast.Ref {
+ var ref = js_ast.Ref{
+ .source_index = p.source.index,
+ .inner_index = @intCast(u32, p.symbols.items.len),
+ };
+
+ try p.symbols.append(Symbol{
+ .kind = kind,
+ .original_name = identifier,
+ .link = null,
+ });
+
+ if (p.options.ts) {
+ try p.ts_use_counts.append(0);
+ }
+
+ return ref;
+ }
+
+ pub fn parseStmt(p: *P, opts: *ParseStatementOptions) !Stmt {
var loc = p.lexer.loc();
switch (p.lexer.token) {
js_lexer.T.t_semicolon => {
p.lexer.next();
- return p.data.add(js_ast.Stmt.init(js_ast.S.Empty{}, loc));
+ return Stmt.empty();
},
js_lexer.T.t_export => {
@@ -864,7 +947,7 @@ const P = struct {
p.lexer.expect(T.t_identifier);
p.lexer.expectOrInsertSemicolon();
- return p.data.add(Stmt.init(S.TypeScript{}, loc));
+ return Stmt.init(S.TypeScript{}, loc);
}
if (p.lexer.isContextualKeyword("async")) {
@@ -876,29 +959,95 @@ const P = struct {
p.lexer.expect(T.t_function);
opts.is_export = true;
- return p.parseFnStmt(loc, opts, asyncRange);
+ return try p.parseFnStmt(loc, opts, asyncRange);
+ }
+
+ if (p.options.ts) {
+ notimpl();
+
+ // switch (p.lexer.identifier) {
+ // "type" => {
+ // // "export type foo = ..."
+ // const typeRange = p.lexer.range();
+ // if (p.lexer.has_newline_before) {
+ // p.lexer.addError(p.source, typeRange.end(), "Unexpected newline after \"type\"");
+ // return;
+ // }
+
+ // },
+ // }
}
+
+ p.lexer.unexpected();
+ lexerpanic();
},
+ T.t_default => {
+ if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) {
+ p.lexer.unexpected();
+ lexerpanic();
+ }
+
+ var defaultLoc = p.lexer.loc();
+ p.lexer.next();
+
+ // TypeScript decorators only work on class declarations
+ // "@decorator export default class Foo {}"
+ // "@decorator export default abstract class Foo {}"
+ if (opts.ts_decorators != null and p.lexer.token != T.t_class and !p.lexer.isContextualKeyword("abstract")) {
+ p.lexer.expected(T.t_class);
+ }
+
+ if (p.lexer.isContextualKeyword("async")) {
+ var async_range = p.lexer.range();
+ p.lexer.next();
+ var defaultName: js_ast.LocRef = undefined;
+ if (p.lexer.token == T.t_function and !p.lexer.has_newline_before) {
+ p.lexer.next();
+ var stmtOpts = ParseStatementOptions{
+ .is_name_optional = true,
+ .lexical_decl = .allow_all,
+ };
+ var stmt = try p.parseFnStmt(loc, &stmtOpts, async_range);
+ if (@as(Stmt.Tag, stmt.data) == .s_type_script) {
+ // This was just a type annotation
+ return stmt;
+ }
+
+ if (stmt.data.s_function.func.name) |name| {
+ defaultName = js_ast.LocRef{ .loc = defaultLoc, .ref = name.ref };
+ } else {
+ defaultName = try p.createDefaultName(defaultLoc);
+ }
+ // this is probably a panic
+ var value = js_ast.StmtOrExpr{ .stmt = &stmt };
+ return s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
+ }
+
+ defaultName = try createDefaultName(p, loc);
+ var expr = p.parseSuffix(p.parseAsyncPrefixExpr(async_range, Level.comma), Level.comma, null, Level.lowest);
+ p.lexer.expectOrInsertSemicolon();
+ // this is probably a panic
+ var value = js_ast.StmtOrExpr{ .expr = &expr };
+ return s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
+ }
+ },
else => {
notimpl();
- return @intCast(NodeIndex, 0);
},
}
},
else => {
notimpl();
- return @intCast(NodeIndex, 0);
},
}
- return @intCast(NodeIndex, 0);
+ return js_ast.Stmt.empty();
}
- pub fn parseStmtsUpTo(p: *P, eend: js_lexer.T, opts: *ParseStatementOptions) !void {
- var data = p.data;
- try data.stmt_list.ensureCapacity(1);
+ pub fn parseStmtsUpTo(p: *P, eend: js_lexer.T, opts: *ParseStatementOptions) ![]Stmt {
+ var stmts = try StmtList.initCapacity(p.allocator, 1);
var returnWithoutSemicolonStart: i32 = -1;
opts.lexical_decl = .allow_all;
@@ -907,7 +1056,7 @@ const P = struct {
run: while (true) {
if (p.lexer.comments_to_preserve_before) |comments| {
for (comments) |comment| {
- try data.add_(Stmt.init(S.Comment{
+ try stmts.append(Stmt.init(S.Comment{
.text = comment.text,
}, p.lexer.loc()));
}
@@ -917,10 +1066,87 @@ const P = struct {
break :run;
}
- const node_index = p.parseStmt(opts) catch break :run;
+ const stmt = p.parseStmt(opts) catch break :run;
+
+ try stmts.append(stmt);
+ }
+
+ return stmts.toOwnedSlice();
+ }
+
+ // 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 {
+ // "async function() {}"
+ if (!p.lexer.has_newline_before and p.lexer.token == T.t_function) {
+ return 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) {}
+ }
- var stmt = p.data.stmt(node_index);
+ 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
+ if (current_scope.contains_direct_eval) {
+ for (current_scope.members) |member| {
+ // Using direct eval when bundling is not a good idea in general because
+ // esbuild must assume that it can potentially reach anything in any of
+ // the containing scopes. We try to make it work but this isn't possible
+ // in some cases.
+ //
+ // For example, symbols imported using an ESM import are a live binding
+ // to the underlying symbol in another file. This is emulated during
+ // scope hoisting by erasing the ESM import and just referencing the
+ // underlying symbol in the flattened bundle directly. However, that
+ // symbol may have a different name which could break uses of direct
+ // eval:
+ //
+ // // Before bundling
+ // import { foo as bar } from './foo.js'
+ // console.log(eval('bar'))
+ //
+ // // After bundling
+ // let foo = 123 // The contents of "foo.js"
+ // console.log(eval('bar'))
+ //
+ // There really isn't any way to fix this. You can't just rename "foo" to
+ // "bar" in the example above because there may be a third bundled file
+ // that also contains direct eval and imports the same symbol with a
+ // different conflicting import alias. And there is no way to store a
+ // live binding to the underlying symbol in a variable with the import's
+ // name so that direct eval can access it:
+ //
+ // // After bundling
+ // let foo = 123 // The contents of "foo.js"
+ // const bar = /* cannot express a live binding to "foo" here */
+ // console.log(eval('bar'))
+ //
+ // Technically a "with" statement could potentially make this work (with
+ // a big hit to performance), but they are deprecated and are unavailable
+ // in strict mode. This is a non-starter since all ESM code is strict mode.
+ //
+ // So while we still try to obey the requirement that all symbol names are
+ // pinned when direct eval is present, we make an exception for top-level
+ // symbols in an ESM file when bundling is enabled. We make no guarantee
+ // that "eval" will be able to reach these symbols and we allow them to be
+ // renamed or removed by tree shaking.
+ // if (p.currentScope.parent == null and p.has_es_module_syntax) {
+ // continue;
+ // }
+
+ p.symbols.items[member.ref.inner_index].must_not_be_renamed = true;
+ }
}
+
+ p.current_scope = current_scope.parent;
+ }
+
+ pub fn parseSuffix(p: *P, left: Expr, level: Level, errors: ?DeferredErrors, flags: Expr.Flags) Expr {
+ var loc = p.lexer.loc();
}
pub fn init(allocator: *std.mem.Allocator, log: logger.Log, source: logger.Source, lexer: js_lexer.Lexer, opts: Parser.Options) !*P {