aboutsummaryrefslogtreecommitdiff
path: root/src/js_parser/js_parser.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-27 01:34:12 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-27 01:34:12 -0700
commit9e0511995ab516cc73bfb54d4b6a9721de2ce823 (patch)
tree24653d460492fbff8afe90cbfa0566042cef6b04 /src/js_parser/js_parser.zig
parenta113603f1009e6ee8bf64ea4453e9513b72b4350 (diff)
parent62d51f7d2e636099f03abcb879475d1193c4022d (diff)
downloadbun-9e0511995ab516cc73bfb54d4b6a9721de2ce823.tar.gz
bun-9e0511995ab516cc73bfb54d4b6a9721de2ce823.tar.zst
bun-9e0511995ab516cc73bfb54d4b6a9721de2ce823.zip
Merge branch 'jarred/ast-again'bun-v0.0.25
Diffstat (limited to 'src/js_parser/js_parser.zig')
-rw-r--r--src/js_parser/js_parser.zig326
1 files changed, 217 insertions, 109 deletions
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 916e3e061..c73ae5718 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -22,6 +22,9 @@ const JSXFactoryName = "JSX";
const JSXAutomaticName = "jsx_module";
const MacroRefs = std.AutoArrayHashMap(Ref, u32);
+const BunJSX = struct {
+ pub threadlocal var bun_jsx_identifier: E.Identifier = undefined;
+};
pub fn ExpressionTransposer(
comptime Kontext: type,
visitor: fn (ptr: *Kontext, arg: Expr, state: anytype) Expr,
@@ -88,6 +91,12 @@ pub const ImportScanner = struct {
.s_import => |st| {
var record: *ImportRecord = &p.import_records.items[st.import_record_index];
+ if (strings.eqlComptime(record.path.namespace, "macro")) {
+ record.is_unused = true;
+ record.path.is_disabled = true;
+ continue;
+ }
+
// The official TypeScript compiler always removes unused imported
// symbols. However, we deliberately deviate from the official
// TypeScript compiler's behavior doing this in a specific scenario:
@@ -1767,7 +1776,7 @@ pub const Parser = struct {
ts: bool = false,
keep_names: bool = true,
omit_runtime_for_tests: bool = false,
- ignore_dce_annotations: bool = true,
+ ignore_dce_annotations: bool = false,
preserve_unused_imports_ts: bool = false,
use_define_for_class_fields: bool = false,
suppress_warnings_about_weird_code: bool = true,
@@ -1846,9 +1855,10 @@ pub const Parser = struct {
// - import 'foo';
// - import("foo")
// - require("foo")
- import_record.is_unused = import_record.kind == .stmt and
+ import_record.is_unused = import_record.is_unused or
+ (import_record.kind == .stmt and
!import_record.was_originally_bare_import and
- !import_record.calls_run_time_re_export_fn;
+ !import_record.calls_run_time_re_export_fn);
}
var iter = scan_pass.used_symbols.iterator();
@@ -1996,7 +2006,7 @@ pub const Parser = struct {
}
// Auto-import JSX
- if (p.options.jsx.parse) {
+ if (ParserType.jsx_transform_type == .react) {
const jsx_filename_symbol = p.symbols.items[p.jsx_filename.ref.inner_index];
{
@@ -2693,8 +2703,9 @@ const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef);
pub fn NewParser(
comptime js_parser_features: ParserFeatures,
) type {
+ const js_parser_jsx = if (FeatureFlags.force_macro) JSXTransformType.macro else js_parser_features.jsx;
const is_typescript_enabled = js_parser_features.typescript;
- const is_jsx_enabled = js_parser_features.jsx != .none;
+ const is_jsx_enabled = js_parser_jsx != .none;
const only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh;
@@ -2706,7 +2717,7 @@ pub fn NewParser(
// public only because of Binding.ToExpr
return struct {
const P = @This();
- pub const jsx_transform_type: JSXTransformType = js_parser_features.jsx;
+ pub const jsx_transform_type: JSXTransformType = js_parser_jsx;
macro_refs: MacroRefs = undefined,
allocator: *std.mem.Allocator,
options: Parser.Options,
@@ -2796,6 +2807,8 @@ pub fn NewParser(
// only applicable when is_react_fast_refresh_enabled
jsx_refresh_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None },
+ bun_jsx_ref: Ref = Ref.None,
+
// Imports (both ES6 and CommonJS) are tracked at the top level
import_records: ImportRecordList,
import_records_for_current_part: List(u32),
@@ -3627,6 +3640,12 @@ pub fn NewParser(
}
},
.macro => {
+ p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable;
+ BunJSX.bun_jsx_identifier = E.Identifier{
+ .ref = p.bun_jsx_ref,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ };
p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable;
},
else => {},
@@ -6150,6 +6169,7 @@ pub fn NewParser(
p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[stmt.import_record_index].path.is_disabled = true;
+ p.import_records.items[stmt.import_record_index].is_internal = true;
}
}
@@ -6157,10 +6177,12 @@ pub fn NewParser(
const name = p.loadNameFromRef(stmt.namespace_ref);
stmt.namespace_ref = try p.declareSymbol(.import, star, name);
if (comptime ParsePassSymbolUsageType != void) {
- p.parse_pass_symbol_uses.put(name, .{
- .ref = stmt.namespace_ref,
- .import_record_index = stmt.import_record_index,
- }) catch unreachable;
+ if (!is_macro) {
+ p.parse_pass_symbol_uses.put(name, .{
+ .ref = stmt.namespace_ref,
+ .import_record_index = stmt.import_record_index,
+ }) catch unreachable;
+ }
}
if (is_macro) {
@@ -6190,10 +6212,12 @@ pub fn NewParser(
try p.is_import_item.put(ref, true);
name_loc.ref = ref;
if (comptime ParsePassSymbolUsageType != void) {
- p.parse_pass_symbol_uses.put(name, .{
- .ref = ref,
- .import_record_index = stmt.import_record_index,
- }) catch unreachable;
+ if (!is_macro) {
+ p.parse_pass_symbol_uses.put(name, .{
+ .ref = ref,
+ .import_record_index = stmt.import_record_index,
+ }) catch unreachable;
+ }
}
if (is_macro) {
@@ -6212,10 +6236,12 @@ pub fn NewParser(
item_refs.putAssumeCapacity(item.alias, LocRef{ .loc = item.name.loc, .ref = ref });
if (comptime ParsePassSymbolUsageType != void) {
- p.parse_pass_symbol_uses.put(name, .{
- .ref = ref,
- .import_record_index = stmt.import_record_index,
- }) catch unreachable;
+ if (!is_macro) {
+ p.parse_pass_symbol_uses.put(name, .{
+ .ref = ref,
+ .import_record_index = stmt.import_record_index,
+ }) catch unreachable;
+ }
}
if (is_macro) {
@@ -9752,9 +9778,12 @@ pub fn NewParser(
},
.t_slash, .t_slash_equals => {
try p.lexer.scanRegExp();
+ // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later
+ defer p.lexer.regex_flags_start = null;
const value = p.lexer.raw();
try p.lexer.next();
- return p.e(E.RegExp{ .value = value }, loc);
+
+ return p.e(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc);
},
.t_void => {
try p.lexer.next();
@@ -10154,7 +10183,11 @@ pub fn NewParser(
// do people do <API_URL>?
fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr {
p.recordUsage(ref);
- return p.e(E.Identifier{ .ref = ref }, loc);
+ return p.e(E.Identifier{
+ .ref = ref,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ }, loc);
}
// Note: The caller has already parsed the "import" keyword
@@ -10733,7 +10766,8 @@ pub fn NewParser(
});
}
- fn visitExpr(p: *P, expr: Expr) Expr {
+ // public for JSNode.JSXWriter usage
+ pub fn visitExpr(p: *P, expr: Expr) Expr {
if (only_scan_imports_and_do_not_visit) {
@compileError("only_scan_imports_and_do_not_visit must not run this.");
}
@@ -10925,52 +10959,9 @@ pub fn NewParser(
.e_jsx_element => |e_| {
switch (comptime jsx_transform_type) {
.macro => {
- const IdentifierOrNodeType = union(Tag) {
- identifier: Expr,
- expression: Expr.Tag,
- pub const Tag = enum { identifier, expression };
- };
- const tag: IdentifierOrNodeType = tagger: {
- if (e_.tag) |_tag| {
- switch (_tag.data) {
- .e_string => |str| {
- if (Expr.Tag.find(str.utf8)) |tagname| {
- break :tagger IdentifierOrNodeType{ .expression = tagname };
- }
-
- p.log.addErrorFmt(
- p.source,
- expr.loc,
- p.allocator,
- "Invalid expression tag: \"<{s}>\". Valid tags are:\n" ++ Expr.Tag.valid_names_list ++ "\n",
- .{str.utf8},
- ) catch unreachable;
- break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) };
- },
- else => {
- break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) };
- },
- }
- } else {
- break :tagger IdentifierOrNodeType{ .expression = Expr.Tag.e_array };
- }
- };
-
- for (e_.properties) |property, i| {
- if (property.kind != .spread) {
- e_.properties[i].key = p.visitExpr(e_.properties[i].key.?);
- }
-
- if (property.value != null) {
- e_.properties[i].value = p.visitExpr(e_.properties[i].value.?);
- }
-
- if (property.initializer != null) {
- e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?);
- }
- }
-
- return p.e(E.Missing{}, expr.loc);
+ const WriterType = js_ast.Macro.JSNode.NewJSXWriter(P);
+ var writer = WriterType.initWriter(p, &BunJSX.bun_jsx_identifier);
+ return writer.writeFunctionCall(e_.*);
},
.react => {
const tag: Expr = tagger: {
@@ -11056,6 +11047,7 @@ pub fn NewParser(
// Either:
// jsxDEV(type, arguments, key, isStaticChildren, source, self)
// jsx(type, arguments, key)
+ const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development;
const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable;
args[0] = tag;
var props = List(G.Property).fromOwnedSlice(p.allocator, e_.properties);
@@ -11134,27 +11126,34 @@ pub fn NewParser(
},
};
- var source = p.allocator.alloc(G.Property, 2) catch unreachable;
- p.recordUsage(p.jsx_filename.ref);
- source[0] = G.Property{
- .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename },
- .value = p.e(E.Identifier{ .ref = p.jsx_filename.ref }, expr.loc),
- };
+ if (include_filename) {
+ var source = p.allocator.alloc(G.Property, 2) catch unreachable;
+ p.recordUsage(p.jsx_filename.ref);
+ source[0] = G.Property{
+ .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename },
+ .value = p.e(E.Identifier{
+ .ref = p.jsx_filename.ref,
+ .can_be_removed_if_unused = true,
+ }, expr.loc),
+ };
- source[1] = G.Property{
- .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber },
- .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc),
- };
+ source[1] = G.Property{
+ .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber },
+ .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc),
+ };
- // Officially, they ask for columnNumber. But I don't see any usages of it in the code!
- // source[2] = G.Property{
- // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber },
- // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc),
- // };
+ // Officially, they ask for columnNumber. But I don't see any usages of it in the code!
+ // source[2] = G.Property{
+ // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber },
+ // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc),
+ // };
+ args[4] = p.e(E.Object{
+ .properties = source,
+ }, expr.loc);
+ } else {
+ args[4] = p.e(E.Object{}, expr.loc);
+ }
- args[4] = p.e(E.Object{
- .properties = source,
- }, expr.loc);
args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc };
}
@@ -11231,6 +11230,89 @@ pub fn NewParser(
const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary;
const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right);
+ if (comptime jsx_transform_type == .macro) {
+ if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) {
+ // foo instanceof <string />
+ // ->
+ // bunJSX.isNodeType(foo, 13)
+
+ // <string /> instanceof foo
+ // ->
+ // bunJSX.isNodeType(foo, 13)
+ var call_args = p.allocator.alloc(Expr, 2) catch unreachable;
+ call_args[0] = e_.left;
+ call_args[1] = e_.right;
+
+ if (e_.right.data == .e_jsx_element) {
+ const jsx_element = e_.right.data.e_jsx_element;
+ if (jsx_element.tag) |tag| {
+ if (tag.data == .e_string) {
+ const tag_string = tag.data.e_string.utf8;
+ if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| {
+ call_args[1] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) };
+ } else {
+ p.log.addRangeErrorFmt(
+ p.source,
+ js_lexer.rangeOfIdentifier(p.source, tag.loc),
+ p.allocator,
+ "Invalid JSX tag: \"{s}\"",
+ .{tag_string},
+ ) catch unreachable;
+ return expr;
+ }
+ }
+ } else {
+ call_args[1] = p.visitExpr(call_args[1]);
+ }
+ } else {
+ call_args[1] = p.visitExpr(call_args[1]);
+ }
+
+ if (e_.left.data == .e_jsx_element) {
+ const jsx_element = e_.left.data.e_jsx_element;
+ if (jsx_element.tag) |tag| {
+ if (tag.data == .e_string) {
+ const tag_string = tag.data.e_string.utf8;
+ if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| {
+ call_args[0] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) };
+ } else {
+ p.log.addRangeErrorFmt(
+ p.source,
+ js_lexer.rangeOfIdentifier(p.source, tag.loc),
+ p.allocator,
+ "Invalid JSX tag: \"{s}\"",
+ .{tag_string},
+ ) catch unreachable;
+ return expr;
+ }
+ }
+ } else {
+ call_args[0] = p.visitExpr(call_args[0]);
+ }
+ } else {
+ call_args[0] = p.visitExpr(call_args[0]);
+ }
+
+ return p.e(
+ E.Call{
+ .target = p.e(
+ E.Dot{
+ .name = "isNodeType",
+ .name_loc = expr.loc,
+ .target = p.e(BunJSX.bun_jsx_identifier, expr.loc),
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ },
+ expr.loc,
+ ),
+ .args = call_args,
+ .can_be_unwrapped_if_unused = true,
+ },
+ expr.loc,
+ );
+ }
+ }
+
e_.left = p.visitExprInOut(e_.left, ExprIn{
.assign_target = e_.op.binaryAssignTarget(),
});
@@ -11857,7 +11939,10 @@ pub fn NewParser(
var has_spread = false;
var has_proto = false;
- for (e_.properties) |*property, i| {
+ var i: usize = 0;
+ while (i < e_.properties.len) : (i += 1) {
+ var property = &e_.properties[i];
+
if (property.kind != .spread) {
property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{}));
const key = property.key.?;
@@ -11949,11 +12034,33 @@ pub fn NewParser(
.has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue,
});
- // TODO: wan about import namespace call
- var has_spread = false;
- for (e_.args) |*arg| {
- arg.* = p.visitExpr(arg.*);
- has_spread = has_spread or @as(Expr.Tag, arg.data) == .e_spread;
+ // Copy the call side effect flag over if this is a known target
+ switch (e_.target.data) {
+ .e_identifier => |ident| {
+ e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or ident.call_can_be_unwrapped_if_unused;
+ },
+ .e_dot => |dot| {
+ e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or dot.call_can_be_unwrapped_if_unused;
+ },
+ else => {},
+ }
+
+ const is_macro_ref: bool = if (comptime FeatureFlags.is_macro_enabled and
+ jsx_transform_type != .macro)
+ e_.target.data == .e_import_identifier and p.macro_refs.contains(e_.target.data.e_import_identifier.ref)
+ else
+ false;
+
+ {
+ const old_ce = p.options.ignore_dce_annotations;
+ defer p.options.ignore_dce_annotations = old_ce;
+ if (is_macro_ref)
+ p.options.ignore_dce_annotations = true;
+
+ for (e_.args) |_, i| {
+ const arg = e_.args[i];
+ e_.args[i] = p.visitExpr(arg);
+ }
}
if (e_.optional_chain == null and @as(Expr.Tag, e_.target.data) == .e_identifier and e_.target.data.e_identifier.ref.eql(p.require_ref)) {
@@ -11969,22 +12076,22 @@ pub fn NewParser(
}
if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
- if (e_.target.data == .e_import_identifier) {
+ if (is_macro_ref) {
const ref = e_.target.data.e_import_identifier.ref;
- if (p.macro_refs.get(ref)) |import_record_id| {
- const name = p.symbols.items[ref.inner_index].original_name;
- const record = &p.import_records.items[import_record_id];
- return p.options.macro_context.call(
- record.path.text,
- p.source.path.sourceDir(),
- p.log,
- p.source,
- record.range,
- expr,
- &.{},
- name,
- ) catch return expr;
- }
+ const import_record_id = p.macro_refs.get(ref).?;
+ const name = p.symbols.items[ref.inner_index].original_name;
+ const record = &p.import_records.items[import_record_id];
+ const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } };
+ return p.options.macro_context.call(
+ record.path.text,
+ p.source.path.sourceDir(),
+ p.log,
+ p.source,
+ record.range,
+ copied,
+ &.{},
+ name,
+ ) catch return expr;
}
}
@@ -12311,6 +12418,7 @@ pub fn NewParser(
.un_typeof, .un_void, .un_not => {
return p.exprCanBeRemovedIfUnused(&ex.value);
},
+
else => {},
}
},
@@ -12571,7 +12679,7 @@ pub fn NewParser(
data.value.expr = p.visitExpr(expr);
// // Optionally preserve the name
- data.value.expr = p.maybeKeepExprSymbolName(expr, "default", was_anonymous_named_expr);
+ data.value.expr = p.maybeKeepExprSymbolName(data.value.expr, "default", was_anonymous_named_expr);
// Discard type-only export default statements
if (is_typescript_enabled) {
@@ -12594,7 +12702,7 @@ pub fn NewParser(
if (p.options.enable_bundling) {
var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable;
export_default_args[0] = p.e(E.Identifier{ .ref = p.exports_ref }, expr.loc);
- export_default_args[1] = expr;
+ export_default_args[1] = data.value.expr;
stmts.append(p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc)) catch unreachable;
return;
}
@@ -15250,7 +15358,7 @@ const JSParserMacro = NewParser(.{
.jsx = .macro,
});
const TSParserMacro = NewParser(.{
- .jsx = .react,
+ .jsx = .macro,
.typescript = true,
});