aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js_ast.zig64
-rw-r--r--src/js_parser.zig587
-rw-r--r--src/logger.zig8
3 files changed, 576 insertions, 83 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 289131bf3..7e81b77b5 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -80,12 +80,18 @@ pub const ImportItemStatus = packed enum {
missing,
};
+pub const AssignTarget = enum {
+ none,
+ replace, // "a = b"
+ update, // "a += b"
+};
+
pub const LocRef = struct { loc: logger.Loc, ref: ?Ref };
pub const Flags = struct {
- // Instead of 4 bytes for booleans, we can store it in 4 bits
- // It will still round up to 1 byte. But that's 3 bytes less!
+ // Instead of 5 bytes for booleans, we can store it in 5 bits
+ // It will still round up to 1 byte. But that's 4 bytes less!
pub const Property = packed struct {
is_computed: bool = false,
is_method: bool = false,
@@ -749,7 +755,7 @@ pub const E = struct {
};
pub const Object = struct {
- properties: []G.Property,
+ properties: []G.Property = &([_]G.Property{}),
comma_after_spread: ?logger.Loc = null,
is_single_line: bool = false,
is_parenthesized: bool = false,
@@ -1153,6 +1159,23 @@ pub const Expr = struct {
pub const EFlags = enum { none, ts_decorator };
+ pub fn isAnonymousNamed(e: *Expr) bool {
+ switch (e.data) {
+ .e_arrow => {
+ return true;
+ },
+ .e_function => |func| {
+ return func.func.name == null;
+ },
+ .e_class => |class| {
+ return class.class_name == null;
+ },
+ else => {
+ return false;
+ },
+ }
+ }
+
pub fn init(exp: anytype, loc: logger.Loc) Expr {
switch (@TypeOf(exp)) {
*E.Array => {
@@ -2132,7 +2155,7 @@ pub const S = struct {
pub const Comment = struct { text: string };
- pub const Directive = struct { value: JavascriptString, legacy_octal_loc: logger.Loc };
+ pub const Directive = struct { value: JavascriptString, legacy_octal_loc: ?logger.Loc = null };
pub const ExportClause = struct { items: []ClauseItem, is_single_line: bool = false };
@@ -2668,8 +2691,6 @@ pub const AstData = struct {
// splitting.
pub const Part = struct {
stmts: []Stmt,
- expr: []Expr,
- bindings: []Binding,
scopes: []*Scope,
// Each is an index into the file-level import record list
@@ -2701,30 +2722,6 @@ pub const Part = struct {
// This is true if this file has been marked as live by the tree shaking
// algorithm.
is_live: bool = false,
-
- pub fn stmtAt(self: *Part, index: StmtNodeIndex) ?Stmt {
- if (std.builtin.mode == std.builtin.Mode.ReleaseFast) {
- return self.stmts[@intCast(usize, index)];
- } else {
- if (self.stmts.len > index) {
- return self.stmts[@intCast(usize, index)];
- }
-
- return null;
- }
- }
-
- pub fn exprAt(self: *Part, index: ExprNodeIndex) ?Expr {
- if (std.builtin.mode == std.builtin.Mode.ReleaseFast) {
- return self.expr[@intCast(usize, index)];
- } else {
- if (self.expr.len > index) {
- return self.expr[@intCast(usize, index)];
- }
-
- return null;
- }
- }
};
pub const StmtOrExpr = union(enum) {
@@ -2816,7 +2813,12 @@ pub const Scope = struct {
pub fn initPtr(allocator: *std.mem.Allocator) !*Scope {
var scope = try allocator.create(Scope);
- scope.members = @TypeOf(scope.members).init(allocator);
+ scope.* = Scope{
+ .members = @TypeOf(scope.members).init(allocator),
+ .children = @TypeOf(scope.children).init(allocator),
+ .generated = @TypeOf(scope.generated).init(allocator),
+ .parent = null,
+ };
return scope;
}
};
diff --git a/src/js_parser.zig b/src/js_parser.zig
index a9775dcfb..67a2c4d66 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -41,6 +41,74 @@ const ExprOrLetStmt = struct {
decls: []G.Decl = &([_]G.Decl{}),
};
+const ExprIn = struct {
+ // This tells us if there are optional chain expressions (EDot, EIndex, or
+ // ECall) that are chained on to this expression. Because of the way the AST
+ // works, chaining expressions on to this expression means they are our
+ // parent expressions.
+ //
+ // Some examples:
+ //
+ // a?.b.c // EDot
+ // a?.b[c] // EIndex
+ // a?.b() // ECall
+ //
+ // Note that this is false if our parent is a node with a OptionalChain
+ // value of OptionalChainStart. That means it's the start of a new chain, so
+ // it's not considered part of this one.
+ //
+ // Some examples:
+ //
+ // a?.b?.c // EDot
+ // a?.b?.[c] // EIndex
+ // a?.b?.() // ECall
+ //
+ // Also note that this is false if our parent is a node with a OptionalChain
+ // value of OptionalChainNone. That means it's outside parentheses, which
+ // means it's no longer part of the chain.
+ //
+ // Some examples:
+ //
+ // (a?.b).c // EDot
+ // (a?.b)[c] // EIndex
+ // (a?.b)() // ECall
+ //
+ has_chain_parent: bool = false,
+
+ // If our parent is an ECall node with an OptionalChain value of
+ // OptionalChainStart, then we will need to store the value for the "this" of
+ // that call somewhere if the current expression is an optional chain that
+ // ends in a property access. That's because the value for "this" will be
+ // used twice: once for the inner optional chain and once for the outer
+ // optional chain.
+ //
+ // Example:
+ //
+ // // Original
+ // a?.b?.();
+ //
+ // // Lowered
+ // var _a;
+ // (_a = a == null ? void 0 : a.b) == null ? void 0 : _a.call(a);
+ //
+ // In the example above we need to store "a" as the value for "this" so we
+ // can substitute it back in when we call "_a" if "_a" is indeed present.
+ // See also "thisArgFunc" and "thisArgWrapFunc" in "exprOut".
+ store_this_arg_for_parent_optional_chain: bool = false,
+
+ // Certain substitutions of identifiers are disallowed for assignment targets.
+ // For example, we shouldn't transform "undefined = 1" into "void 0 = 1". This
+ // isn't something real-world code would do but it matters for conformance
+ // tests.
+ assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none,
+};
+
+const ExprOut = struct {
+ // True if the child node is an optional chain node (EDot, EIndex, or ECall
+ // with an IsOptionalChain value of true)
+ child_contains_optional_chain: bool = false,
+};
+
const Tup = std.meta.Tuple;
// This function exists to tie all of these checks together in one place
@@ -48,6 +116,17 @@ fn isEvalOrArguments(name: string) bool {
return strings.eql(name, "eval") or strings.eql(name, "arguments");
}
+const PrependTempRefsOpts = struct {
+ fn_body_loc: ?logger.Loc = null,
+ kind: StmtsKind = StmtsKind.none,
+};
+
+pub const StmtsKind = enum {
+ none,
+ loop_body,
+ fn_body,
+};
+
fn notimpl() noreturn {
std.debug.panic("Not implemented yet!!", .{});
}
@@ -63,8 +142,8 @@ fn fail() noreturn {
const ExprBindingTuple = struct { expr: ?ExprNodeIndex = null, binding: ?Binding = null, override_expr: ?ExprNodeIndex = null };
const TempRef = struct {
- ref: js_ast.Ref,
- value: *js_ast.Expr,
+ ref: Ref,
+ value: ?Expr = null,
};
const ImportNamespaceCallOrConstruct = struct {
@@ -173,18 +252,18 @@ const FnOnlyDataVisit = struct {
// This is a reference to the magic "arguments" variable that exists inside
// functions in JavaScript. It will be non-nil inside functions and nil
// otherwise.
- arguments_ref: *js_ast.Ref,
+ arguments_ref: ?js_ast.Ref = null,
// Arrow functions don't capture the value of "this" and "arguments". Instead,
// the values are inherited from the surrounding context. If arrow functions
// are turned into regular functions due to lowering, we will need to generate
// local variables to capture these values so they are preserved correctly.
- this_capture_ref: *js_ast.Ref,
- arguments_capture_ref: *js_ast.Ref,
+ this_capture_ref: ?js_ast.Ref = null,
+ arguments_capture_ref: ?js_ast.Ref = null,
// Inside a static class property initializer, "this" expressions should be
// replaced with the class name.
- this_class_static_ref: *js_ast.Ref,
+ this_class_static_ref: ?js_ast.Ref = null,
// If we're inside an async arrow function and async functions are not
// supported, then we will have to convert that arrow function to a generator
@@ -298,6 +377,35 @@ pub const Parser = struct {
var opts = ParseStatementOptions{ .is_module_scope = true };
const stmts = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts);
try p.prepareForVisitPass();
+
+ // ESM is always strict mode. I don't think we need this.
+ // // Strip off a leading "use strict" directive when not bundling
+ // var directive = "";
+
+ // Insert a variable for "import.meta" at the top of the file if it was used.
+ // We don't need to worry about "use strict" directives because this only
+ // happens when bundling, in which case we are flatting the module scopes of
+ // all modules together anyway so such directives are meaningless.
+ if (!p.import_meta_ref.isNull()) {
+ // heap so it lives beyond this function call
+ var decls = try p.allocator.alloc(G.Decl, 1);
+ decls[0] = Decl{ .binding = p.b(B.Identifier{
+ .ref = p.import_meta_ref,
+ }, logger.Loc.Empty), .value = p.e(E.Object{}, logger.Loc.Empty) };
+ var importMetaStatement = p.s(S.Local{
+ .kind = .k_const,
+ .decls = decls,
+ }, logger.Loc.Empty);
+ }
+
+ var parts = try List(js_ast.Part).initCapacity(p.allocator, 1);
+ try p.appendPart(parts, stmts);
+
+ // Pop the module scope to apply the "ContainsDirectEval" rules
+ p.popScope();
+
+ result = p.toAST(parts);
+ result.source_map_comment = p.lexer.source_mapping_url;
}
return result;
@@ -325,6 +433,11 @@ pub const Parser = struct {
}
};
+const FindSymbolResult = struct {
+ ref: Ref,
+ declare_loc: ?logger.Loc = null,
+ is_inside_with_scope: bool = false,
+};
const ExportClauseResult = struct { clauses: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), is_single_line: bool = false };
const DeferredTsDecorators = struct {
@@ -373,7 +486,7 @@ const P = struct {
allocated_names: List(string),
latest_arrow_arg_loc: logger.Loc = logger.Loc.Empty,
forbid_suffix_after_as_loc: logger.Loc = logger.Loc.Empty,
- current_scope: ?*js_ast.Scope = null,
+ current_scope: *js_ast.Scope = null,
scopes_for_current_part: List(*js_ast.Scope),
symbols: List(js_ast.Symbol),
ts_use_counts: List(u32),
@@ -604,19 +717,72 @@ const P = struct {
parser.relocated_top_level_vars.deinit();
}
- pub fn findSymbol(self: *P, loc: logger.Loc, name: string) ?js_ast.Symbol {
- return null;
+ pub fn findSymbol(p: *P, loc: logger.Loc, name: string) !FindSymbolResult {
+ var ref: Ref = undefined;
+ var declare_loc: logger.Loc = undefined;
+ var is_inside_with_scope = false;
+ var did_forbid_argumen = false;
+ var scope = p.current_scope;
+
+ while (true) {
+
+ // Track if we're inside a "with" statement body
+ if (scope.kind == .with) {
+ is_inside_with_scope = true;
+ }
+
+ // Forbid referencing "arguments" inside class bodies
+ if (scope.forbid_arguments and strings.eql(name, "arguments") and !did_forbid_argumen) {
+ const r = js_lexer.rangeOfIdentifier(&p.source, loc);
+ p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot access \"{s}\" here", .{name}) catch unreachable;
+ did_forbid_argumen = true;
+ }
+
+ // Is the symbol a member of this scope?
+ if (scope.members.get(name)) |member| {
+ ref = member.ref;
+ declare_loc = member.loc;
+ break;
+ }
+
+ if (scope.parent) |parent| {
+ scope = parent;
+ } else {
+ // Allocate an "unbound" symbol
+ p.checkForNonBMPCodePoint(loc, name);
+ ref = try p.newSymbol(.unbound, name);
+ declare_loc = loc;
+ try p.module_scope.members.put(name, js_ast.Scope.Member{ .ref = ref, .loc = logger.Loc.Empty });
+ break;
+ }
+ }
+
+ // If we had to pass through a "with" statement body to get to the symbol
+ // declaration, then this reference could potentially also refer to a
+ // property on the target object of the "with" statement. We must not rename
+ // it or we risk changing the behavior of the code.
+ if (is_inside_with_scope) {
+ p.symbols.items[ref.inner_index].must_not_be_renamed = true;
+ }
+
+ // Track how many times we've referenced this symbol
+ p.recordUsage(&ref);
+ return FindSymbolResult{
+ .ref = ref,
+ .declare_loc = declare_loc,
+ .is_inside_with_scope = is_inside_with_scope,
+ };
}
- pub fn recordUsage(self: *P, ref: *js_ast.Ref) void {
+ pub fn recordUsage(p: *P, ref: *js_ast.Ref) void {
// The use count stored in the symbol is used for generating symbol names
// during minification. These counts shouldn't include references inside dead
// code regions since those will be culled.
if (!p.is_control_flow_dead) {
- p.symbols[ref.inner_index].use_count_estimate += 1;
- var use = p.symbol_uses[ref];
+ p.symbols.items[ref.inner_index].use_count_estimate += 1;
+ var use = p.symbol_uses.get(ref.*) orelse unreachable;
use.count_estimate += 1;
- p.symbol_uses.put(ref, use);
+ p.symbol_uses.put(ref.*, use) catch unreachable;
}
// The correctness of TypeScript-to-JavaScript conversion relies on accurate
@@ -747,7 +913,7 @@ const P = struct {
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;
- p.module_scope = p.current_scope orelse unreachable;
+ p.module_scope = p.current_scope;
p.has_es_module_syntax = p.es6_import_keyword.len > 0 or p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0;
// ECMAScript modules are always interpreted as strict mode. This has to be
@@ -801,7 +967,7 @@ const P = struct {
}
pub fn pushScopeForParsePass(p: *P, kind: js_ast.Scope.Kind, loc: logger.Loc) !usize {
- var parent = p.current_scope orelse unreachable;
+ var parent = p.current_scope;
var scope = try js_ast.Scope.initPtr(p.allocator);
scope.kind = kind;
scope.parent = parent;
@@ -1059,7 +1225,7 @@ const P = struct {
pub fn popAndDiscardScope(p: *P, scope_index: usize) void {
// Move up to the parent scope
- var to_discard = p.current_scope orelse unreachable;
+ var to_discard = p.current_scope;
var parent = to_discard.parent orelse unreachable;
p.current_scope = parent;
@@ -1161,7 +1327,7 @@ const P = struct {
const name = js_ast.LocRef{ .loc = loc, .ref = try p.newSymbol(Symbol.Kind.other, identifier) };
- var scope = p.current_scope orelse unreachable;
+ var scope = p.current_scope;
try scope.generated.append(name.ref orelse unreachable);
@@ -2152,7 +2318,7 @@ const P = struct {
var path_name = fs.PathName.init(strings.append(p.allocator, "import_", path.text) catch unreachable);
const name = try path_name.nonUniqueNameString(p.allocator);
stmt.namespace_ref = try p.newSymbol(.other, name);
- var scope: *Scope = p.current_scope orelse unreachable;
+ var scope: *Scope = p.current_scope;
try scope.generated.append(stmt.namespace_ref);
}
@@ -2417,7 +2583,7 @@ const P = struct {
pub fn discardScopesUpTo(p: *P, scope_index: usize) void {
// Remove any direct children from their parent
- var scope = p.current_scope orelse unreachable;
+ var scope = p.current_scope;
var children = scope.children;
for (p.scopes_in_order.items[scope_index..]) |child| {
if (child.scope.parent == p.current_scope) {
@@ -3017,9 +3183,80 @@ const P = struct {
break :run;
}
- const stmt = p.parseStmt(opts) catch break :run;
+ var stmt = p.parseStmt(opts) catch break :run;
+
+ // Skip TypeScript types entirely
+ if (p.options.ts) {
+ switch (stmt.data) {
+ .s_type_script => {
+ continue;
+ },
+ else => {},
+ }
+ }
+
+ // Parse one or more directives at the beginning
+ if (isDirectivePrologue) {
+ isDirectivePrologue = false;
+ switch (stmt.data) {
+ .s_expr => |expr| {
+ switch (expr.value.data) {
+ .e_string => |str| {
+ if (!str.prefer_template) {
+ stmt.data = Stmt.Data{
+ .s_directive = p.m(S.Directive{
+ .value = str.value,
+ // .legacy_octal_loc = str.legacy_octal_loc,
+ }),
+ };
+ isDirectivePrologue = true;
+
+ if (strings.eqlUtf16("use strict", str.value)) {
+ // Track "use strict" directives
+ p.current_scope.strict_mode = .explicit_strict_mode;
+ } else if (strings.eqlUtf16("use asm", str.value)) {
+ stmt.data = Stmt.Data{ .s_empty = p.m(S.Empty{}) };
+ }
+ }
+ },
+ else => {},
+ }
+ },
+ else => {},
+ }
+ }
try stmts.append(stmt);
+
+ // Warn about ASI and return statements. Here's an example of code with
+ // this problem: https://github.com/rollup/rollup/issues/3729
+ if (!p.options.suppress_warnings_about_weird_code) {
+ var needsCheck = true;
+ switch (stmt.data) {
+ .s_return => |ret| {
+ if (ret.value == null and !p.latest_return_had_semicolon) {
+ returnWithoutSemicolonStart = stmt.loc.start;
+ needsCheck = false;
+ }
+ },
+ else => {},
+ }
+
+ if (needsCheck and returnWithoutSemicolonStart != -1) {
+ switch (stmt.data) {
+ .s_expr => |exp| {
+ try p.log.addWarning(
+ p.source,
+ logger.Loc{ .start = returnWithoutSemicolonStart + 6 },
+ "The following expression is not returned because of an automatically-inserted semicolon",
+ );
+ },
+ else => {},
+ }
+
+ returnWithoutSemicolonStart = -1;
+ }
+ }
}
return stmts.toOwnedSlice();
@@ -3059,40 +3296,39 @@ const P = struct {
// },
}
- if (p.current_scope) |scope| {
- if (p.isStrictMode()) {
- var why: string = "";
- var notes: []logger.Data = 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;
- },
- else => {},
- }
- 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)});
- }
-
- try p.log.addRangeErrorWithNotes(p.source, r, try 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()) {
- try p.log.addRangeError(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used with \"esm\" due to strict mode", .{text}));
+ var scope = p.current_scope;
+ if (p.isStrictMode()) {
+ var why: string = "";
+ var notes: []logger.Data = 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;
+ },
+ else => {},
+ }
+ 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)});
}
+
+ try p.log.addRangeErrorWithNotes(p.source, r, try 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()) {
+ try p.log.addRangeError(p.source, r, try 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;
+ return p.current_scope.strict_mode != .sloppy_mode;
}
pub fn isStrictModeOutputFormat(p: *P) bool {
@@ -3110,7 +3346,7 @@ const P = struct {
// Allocate a new symbol
var ref = try p.newSymbol(kind, name);
- const scope = p.current_scope orelse unreachable;
+ const scope = p.current_scope;
if (scope.members.get(name)) |existing| {
var symbol: Symbol = p.symbols.items[@intCast(usize, existing.ref.inner_index)];
@@ -3505,7 +3741,7 @@ const P = struct {
}
pub fn popScope(p: *P) void {
- const current_scope = p.current_scope orelse unreachable;
+ const current_scope = p.current_scope;
// We cannot rename anything inside a scope containing a direct eval() call
if (current_scope.contains_direct_eval) {
var iter = current_scope.members.iterator();
@@ -3560,7 +3796,7 @@ const P = struct {
}
}
- p.current_scope = current_scope.parent;
+ p.current_scope = current_scope.parent orelse std.debug.panic("Internal error: attempted to call popScope() on the topmost scope", .{});
}
pub fn markExprAsParenthesized(p: *P, expr: *Expr) void {
@@ -5544,6 +5780,253 @@ const P = struct {
return p._parsePrefix(level, errors orelse &DeferredErrors.None, flags);
}
+ pub fn appendPart(p: *P, parts: List(js_ast.Part), stmts: []Stmt) !void {
+ p.symbol_uses = SymbolUseMap.init(p.allocator);
+ p.declared_symbols.deinit();
+ p.import_records_for_current_part.deinit();
+ p.scopes_for_current_part.deinit();
+ var opts = PrependTempRefsOpts{};
+ var partStmts = List(Stmt).fromOwnedSlice(p.allocator, stmts);
+ try p.visitStmtsAndPrependTempRefs(&partStmts, &opts);
+
+ // Insert any relocated variable statements now
+ if (p.relocated_top_level_vars.items.len > 0) {
+ var already_declared = RefBoolMap.init(p.allocator);
+
+ // Follow links because "var" declarations may be merged due to hoisting
+
+ // while (true) {
+ // const link = p.symbols.items[local.ref.inner_index].link;
+ // }
+ }
+ // TODO: here
+ }
+
+ pub fn visitStmtsAndPrependTempRefs(p: *P, stmts: *List(Stmt), opts: *PrependTempRefsOpts) !void {
+ var old_temp_refs = p.temp_refs_to_declare;
+ var old_temp_ref_count = p.temp_ref_count;
+ p.temp_refs_to_declare.deinit();
+ p.temp_refs_to_declare = @TypeOf(p.temp_refs_to_declare).init(p.allocator);
+ p.temp_ref_count = 0;
+
+ try p.visitStmts(stmts, opts.kind);
+
+ // Prepend values for "this" and "arguments"
+ if (opts.fn_body_loc != null) {
+ // Capture "this"
+ if (p.fn_only_data_visit.this_capture_ref) |ref| {
+ try p.temp_refs_to_declare.append(TempRef{
+ .ref = ref,
+ .value = p.e(E.This{}, opts.fn_body_loc orelse std.debug.panic("Internal error: Expected opts.fn_body_loc to exist", .{})),
+ });
+ }
+ }
+ }
+
+ pub fn recordDeclaredSymbol(p: *P, ref: Ref) !void {
+ try p.declared_symbols.append(js_ast.DeclaredSymbol{
+ .ref = ref,
+ .is_top_level = p.current_scope == p.module_scope,
+ });
+ }
+
+ pub fn visitExpr(p: *P, expr: Expr) Expr {
+ return p.visitExprInOut(expr, in);
+ }
+
+ pub fn visitExprInOut(p: *P, expr: Expr, in: ExprIn) Expr {}
+
+ pub fn visitAndAppendStmt(p: *P, stmts: *List(Stmt), stmt: *Stmt) !void {
+ switch (stmt.data) {
+ // These don't contain anything to traverse
+
+ .s_debugger, .s_empty, .s_comment => {},
+ .s_type_script => |data| {
+ // Erase TypeScript constructs from the output completely
+ return;
+ },
+ .s_directive => |data| {
+ // if p.isStrictMode() && s.LegacyOctalLoc.Start > 0 {
+ // p.markStrictModeFeature(legacyOctalEscape, p.source.RangeOfLegacyOctalEscape(s.LegacyOctalLoc), "")
+ // }
+ return;
+ },
+ .s_import => |data| {
+ try p.recordDeclaredSymbol(data.namespace_ref);
+
+ if (data.default_name) |default_name| {
+ try p.recordDeclaredSymbol(default_name.ref orelse unreachable);
+ }
+
+ if (data.items.len > 0) {
+ for (data.items) |*item| {
+ try p.recordDeclaredSymbol(item.name.ref orelse unreachable);
+ }
+ }
+ },
+ .s_export_clause => |data| {
+ // "export {foo}"
+ var end: usize = 0;
+ for (data.items) |*item| {
+ const name = p.loadNameFromRef(item.name.ref orelse unreachable);
+ const symbol = try p.findSymbol(item.alias_loc, name);
+ const ref = symbol.ref;
+
+ if (p.symbols.items[ref.inner_index].kind == .unbound) {
+ // Silently strip exports of non-local symbols in TypeScript, since
+ // those likely correspond to type-only exports. But report exports of
+ // non-local symbols as errors in JavaScript.
+ if (!p.options.ts) {
+ const r = js_lexer.rangeOfIdentifier(&p.source, item.name.loc);
+ try p.log.addRangeErrorFmt(p.source, r, p.allocator, "\"{s}\" is not declared in this file", .{name});
+ continue;
+ }
+ continue;
+ }
+
+ item.name.ref = ref;
+ data.items[end] = item.*;
+ end += 1;
+ }
+ // esbuild: "Note: do not remove empty export statements since TypeScript uses them as module markers"
+ // jarred: does that mean we can remove them here, since we're not bundling for production?
+ data.items = data.items[0..end];
+ },
+ .s_export_from => |data| {
+ // "export {foo} from 'path'"
+ const name = p.loadNameFromRef(data.namespace_ref);
+ data.namespace_ref = try p.newSymbol(.other, name);
+ try p.current_scope.generated.append(data.namespace_ref);
+ try p.recordDeclaredSymbol(data.namespace_ref);
+
+ // This is a re-export and the symbols created here are used to reference
+ for (data.items) |*item| {
+ const _name = p.loadNameFromRef(item.name.ref orelse unreachable);
+ const ref = try p.newSymbol(.other, _name);
+ try p.current_scope.generated.append(data.namespace_ref);
+ try p.recordDeclaredSymbol(data.namespace_ref);
+ item.name.ref = ref;
+ }
+ },
+ .s_export_star => |data| {
+ // "export {foo} from 'path'"
+ const name = p.loadNameFromRef(data.namespace_ref);
+ data.namespace_ref = try p.newSymbol(.other, name);
+ try p.current_scope.generated.append(data.namespace_ref);
+ try p.recordDeclaredSymbol(data.namespace_ref);
+
+ // "export * as ns from 'path'"
+ if (data.alias) |alias| {
+ // "import * as ns from 'path'"
+ // "export {ns}"
+
+ // jarred: For now, just always do this transform.
+ // because Safari doesn't support it and I've seen cases where this breaks
+ // TODO: backport unsupportedJSFeatures map
+ p.recordUsage(&data.namespace_ref);
+ try stmts.ensureCapacity(stmts.items.len + 2);
+ stmts.appendAssumeCapacity(p.s(S.Import{ .namespace_ref = data.namespace_ref, .star_name_loc = alias.loc, .import_record_index = data.import_record_index }, stmt.loc));
+
+ var items = try List(js_ast.ClauseItem).initCapacity(p.allocator, 1);
+ items.appendAssumeCapacity(js_ast.ClauseItem{ .alias = alias.original_name, .original_name = alias.original_name, .alias_loc = alias.loc, .name = LocRef{ .loc = alias.loc, .ref = data.namespace_ref } });
+ stmts.appendAssumeCapacity(p.s(S.ExportClause{ .items = items.toOwnedSlice(), .is_single_line = true }, stmt.loc));
+ }
+ },
+ .s_export_default => |data| {
+ try p.recordDeclaredSymbol(data.default_name.ref orelse unreachable);
+
+ switch (data.value) {
+ .expr => |*expr| {
+ const was_anonymous_named_expr = expr.isAnonymousNamed();
+ data.value.expr = p.m(p.visitExpr(expr));
+
+ // Optionally preserve the name
+ data.value.expr = p.maybeKeepExprSymbolName(expr, "default", was_anonymous_named_expr);
+
+ // Discard type-only export default statements
+ if (p.options.ts) {
+ switch (expr.data) {
+ .e_identifier => |ident| {
+ const symbol = p.symbols.items[ident.ref.inner_index];
+ if (symbol.kind == .unbound) {
+ if (p.local_type_names.get(symbol.original_name)) |local_type| {
+ if (local_type.value) {
+ return;
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ }
+ },
+
+ .stmt => |st| {},
+ }
+ },
+ .s_export_equals => |data| {},
+ .s_break => |data| {},
+ .s_continue => |data| {},
+ .s_label => |data| {},
+ .s_local => |data| {},
+ .s_expr => |data| {},
+ .s_throw => |data| {},
+ .s_return => |data| {},
+ .s_block => |data| {},
+ .s_with => |data| {},
+ .s_while => |data| {},
+ .s_do_while => |data| {},
+ .s_if => |data| {},
+ .s_for => |data| {},
+ .s_for_in => |data| {},
+ .s_for_of => |data| {},
+ .s_try => |data| {},
+ .s_switch => |data| {},
+ .s_function => |data| {},
+ .s_class => |data| {},
+ .s_enum => |data| {},
+ .s_namespace => |data| {},
+ else => {},
+ }
+
+ // if we get this far, it stays
+ try stmts.append(stmt.*);
+ }
+
+ fn visitStmts(p: *P, stmts: *List(Stmt), kind: StmtsKind) !void {
+ // Save the current control-flow liveness. This represents if we are
+ // currently inside an "if (false) { ... }" block.
+ var old_is_control_flow_dead = p.is_control_flow_dead;
+
+ // visit all statements first
+ var visited = try List(Stmt).initCapacity(p.allocator, stmts.items.len);
+ var before = List(Stmt).init(p.allocator);
+ var after = List(Stmt).init(p.allocator);
+ for (stmts.items) |*stmt| {
+ switch (stmt.data) {
+ .s_export_equals => {
+ // TypeScript "export = value;" becomes "module.exports = value;". This
+ // must happen at the end after everything is parsed because TypeScript
+ // moves this statement to the end when it generates code.
+ try p.visitAndAppendStmt(&after, stmt);
+ continue;
+ },
+ .s_function => |data| {
+ // Manually hoist block-level function declarations to preserve semantics.
+ // This is only done for function declarations that are not generators
+ // or async functions, since this is a backwards-compatibility hack from
+ // Annex B of the JavaScript standard.
+ if (!p.current_scope.kindStopsHoisting() and p.symbols.items[data.func.name.?.ref.?.inner_index].kind == .hoisted_function) {
+ try p.visitAndAppendStmt(&before, stmt);
+ continue;
+ }
+ },
+ else => {},
+ }
+ try p.visitAndAppendStmt(&visited, stmt);
+ }
+ }
+
fn extractDeclsForBinding(binding: Binding, decls: *List(G.Decl)) !void {
switch (binding.data) {
.b_property, .b_missing => {},
diff --git a/src/logger.zig b/src/logger.zig
index 634064086..47253728c 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -178,6 +178,14 @@ pub const Log = struct {
});
}
+ pub fn addWarning(log: *Log, source: ?Source, l: Loc, text: string) !void {
+ log.warnings += 1;
+ try log.addMsg(Msg{
+ .kind = .warn,
+ .data = rangeData(source, Range{ .loc = l }, text),
+ });
+ }
+
pub fn addRangeDebug(log: *Log, source: ?Source, r: Range, text: string) !void {
try log.addMsg(Msg{
.kind = .debug,