aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/js_ast.zig132
-rw-r--r--src/js_parser.zig22
2 files changed, 146 insertions, 8 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index c0e4de6d9..85ccc7a4d 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -1430,6 +1430,49 @@ pub const E = struct {
pub const Number = struct {
value: f64,
+ const double_digit = [_]string{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100" };
+ const neg_double_digit = [_]string{ "-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", "-11", "-12", "-13", "-14", "-15", "-16", "-17", "-18", "-19", "-20", "-21", "-22", "-23", "-24", "-25", "-26", "-27", "-28", "-29", "-30", "-31", "-32", "-33", "-34", "-35", "-36", "-37", "-38", "-39", "-40", "-41", "-42", "-43", "-44", "-45", "-46", "-47", "-48", "-49", "-50", "-51", "-52", "-53", "-54", "-55", "-56", "-57", "-58", "-59", "-60", "-61", "-62", "-63", "-64", "-65", "-66", "-67", "-68", "-69", "-70", "-71", "-72", "-73", "-74", "-75", "-76", "-77", "-78", "-79", "-80", "-81", "-82", "-83", "-84", "-85", "-86", "-87", "-88", "-89", "-90", "-91", "-92", "-93", "-94", "-95", "-96", "-97", "-98", "-99", "-100" };
+
+ /// String concatenation with numbers is required by the TypeScript compiler for
+ /// "constant expression" handling in enums. However, we don't want to introduce
+ /// correctness bugs by accidentally stringifying a number differently than how
+ /// a real JavaScript VM would do it. So we are conservative and we only do this
+ /// when we know it'll be the same result.
+ pub fn toStringSafely(this: Number, allocator: std.mem.Allocator) ?string {
+ return toStringFromF64Safe(this.value, allocator);
+ }
+
+ pub fn toStringFromF64Safe(value: f64, allocator: std.mem.Allocator) ?string {
+ if (value == @trunc(value)) {
+ const int_value = @floatToInt(i64, value);
+ const abs = @intCast(u64, std.math.absInt(int_value) catch return null);
+ if (abs < double_digit.len) {
+ return if (value < 0)
+ neg_double_digit[abs]
+ else
+ double_digit[abs];
+ }
+
+ if (abs <= std.math.maxInt(i32)) {
+ return std.fmt.allocPrint(allocator, "{d}", .{@intCast(i32, int_value)}) catch return null;
+ }
+ }
+
+ if (std.math.isNan(value)) {
+ return "NaN";
+ }
+
+ if (std.math.isNegativeInf(value)) {
+ return "-Infinity";
+ }
+
+ if (std.math.isInf(value)) {
+ return "Infinity";
+ }
+
+ return null;
+ }
+
pub inline fn toU64(self: Number) u64 {
@setRuntimeSafety(false);
return @floatToInt(u64, @max(@trunc(self.value), 0));
@@ -2022,6 +2065,95 @@ pub const E = struct {
tag: ?ExprNodeIndex = null,
head: E.String,
parts: []TemplatePart = &([_]TemplatePart{}),
+
+ /// "`a${'b'}c`" => "`abc`"
+ pub fn fold(
+ this: *Template,
+ allocator: std.mem.Allocator,
+ loc: logger.Loc,
+ ) Expr {
+ if (this.tag != null) {
+ return Expr{
+ .data = .{
+ .e_template = this,
+ },
+ .loc = loc,
+ };
+ }
+
+ // we only fold utf-8/ascii for now
+ if (this.parts.len == 0 or !this.head.isUTF8()) {
+ return Expr.init(E.String, this.head, loc);
+ }
+
+ var parts = std.ArrayList(TemplatePart).initCapacity(allocator, this.parts.len) catch unreachable;
+ var head = Expr.init(E.String, this.head, loc);
+ for (this.parts) |part_| {
+ var part = part_;
+
+ switch (part.value.data) {
+ .e_number => {
+ if (part.value.data.e_number.toStringSafely(allocator)) |s| {
+ part.value = Expr.init(E.String, E.String.init(s), part.value.loc);
+ }
+ },
+ .e_null => {
+ part.value = Expr.init(E.String, E.String.init("null"), part.value.loc);
+ },
+ .e_boolean => {
+ part.value = Expr.init(E.String, E.String.init(if (part.value.data.e_boolean.value)
+ "true"
+ else
+ "false"), part.value.loc);
+ },
+ .e_undefined => {
+ part.value = Expr.init(E.String, E.String.init("undefined"), part.value.loc);
+ },
+ else => {},
+ }
+
+ if (part.value.data == .e_string and part.tail.isUTF8() and part.value.data.e_string.isUTF8()) {
+ if (parts.items.len == 0) {
+ if (part.value.data.e_string.len() > 0) {
+ head.data.e_string.push(part.value.data.e_string);
+ }
+
+ if (part.tail.len() > 0) {
+ head.data.e_string.push(Expr.init(E.String, part.tail, part.tail_loc).data.e_string);
+ }
+
+ continue;
+ } else {
+ var prev_part = &parts.items[parts.items.len - 1];
+
+ if (part.value.data.e_string.len() > 0) {
+ prev_part.tail.push(part.value.data.e_string);
+ }
+
+ if (part.tail.len() > 0) {
+ prev_part.tail.push(Expr.init(E.String, part.tail, part.tail_loc).data.e_string);
+ }
+ }
+ } else {
+ parts.appendAssumeCapacity(part);
+ }
+ }
+
+ if (parts.items.len == 0) {
+ parts.deinit();
+
+ return head;
+ }
+
+ return Expr.init(
+ E.Template,
+ E.Template{
+ .tag = null,
+ .parts = parts.items,
+ .head = head.data.e_string.*,
+ },
+ loc,
+ );
};
pub const RegExp = struct {
diff --git a/src/js_parser.zig b/src/js_parser.zig
index be62d36c2..3508a3828 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -74,8 +74,6 @@ const StringHashMapUnamanged = bun.StringHashMapUnmanaged;
const ObjectPool = @import("./pool.zig").ObjectPool;
const NodeFallbackModules = @import("./node_fallbacks.zig");
-const RefExprMap = std.ArrayHashMapUnmanaged(Ref, Expr, RefHashCtx, false);
-
const SkipTypeParameterResult = enum {
did_not_skip_anything,
could_be_type_cast,
@@ -4751,7 +4749,7 @@ fn NewParser_(
scope_order_to_visit: []ScopeOrder = &([_]ScopeOrder{}),
- const_values: RefExprMap = .{},
+ const_values: js_ast.Ast.ConstValuesMap = .{},
pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr {
// The argument must be a string
@@ -5398,6 +5396,8 @@ fn NewParser_(
if (p.options.features.inlining) {
if (p.const_values.get(ref)) |replacement| {
+ // TODO:
+ // p.ignoreUsage(ref);
return replacement;
}
}
@@ -15028,6 +15028,13 @@ fn NewParser_(
for (e_.parts) |*part| {
part.value = p.visitExpr(part.value);
}
+
+ // When mangling, inline string values into the template literal. Note that
+ // it may no longer be a template literal after this point (it may turn into
+ // a plain string literal instead).
+ if (p.should_fold_typescript_constant_expressions or p.options.features.inlining) {
+ return e_.fold(p.allocator, expr.loc);
+ }
},
.inline_identifier => |id| {
@@ -17073,11 +17080,7 @@ fn NewParser_(
.e_string => |str| {
// minify "long-string".length to 11
if (strings.eqlComptime(name, "length")) {
- // don't handle UTF-16 strings for now
- if (str.is_utf16)
- return null;
-
- return p.newExpr(E.Number{ .value = @intToFloat(f64, str.len()) }, loc);
+ return p.newExpr(E.Number{ .value = @intToFloat(f64, str.javascriptLength()) }, loc);
}
},
.e_object => |obj| {
@@ -20814,6 +20817,9 @@ fn NewParser_(
// .top_Level_await_keyword = p.top_level_await_keyword,
.bun_plugin = p.bun_plugin,
.commonjs_named_exports = p.commonjs_named_exports,
+
+ // TODO:
+ // .const_values = p.const_values,
};
}