diff --git a/src/css/context.zig b/src/css/context.zig index db6b3964a2a019..09c9e59373c206 100644 --- a/src/css/context.zig +++ b/src/css/context.zig @@ -25,9 +25,9 @@ pub const SupportsEntry = struct { important_declarations: ArrayList(css.Property), pub fn deinit(this: *@This(), allocator: std.mem.Allocator) void { - _ = this; // autofix - _ = allocator; // autofix - @panic(css.todo_stuff.depth); + this.condition.deinit(allocator); + css.deepDeinit(css.Property, allocator, &this.declarations); + css.deepDeinit(css.Property, allocator, &this.important_declarations); } }; diff --git a/src/css/css_modules.zig b/src/css/css_modules.zig index 14767a4a8c4a42..e476b03b4c2bd1 100644 --- a/src/css/css_modules.zig +++ b/src/css/css_modules.zig @@ -70,17 +70,71 @@ pub const CssModule = struct { // TODO: deinit } + pub fn getReference(this: *CssModule, allocator: Allocator, name: []const u8, source_index: u32) void { + const gop = this.exports_by_source_index.items[source_index].getOrPut(allocator, name) catch bun.outOfMemory(); + if (gop.found_existing) { + gop.value_ptr.is_referenced = true; + } else { + gop.value_ptr.* = CssModuleExport{ + .name = this.config.pattern.writeToString(allocator, .{}, this.hashes.items[source_index], this.sources.items[source_index], name), + .composes = .{}, + .is_referenced = true, + }; + } + } + pub fn referenceDashed( this: *CssModule, + allocator: std.mem.Allocator, name: []const u8, from: *const ?css.css_properties.css_modules.Specifier, source_index: u32, ) ?[]const u8 { - _ = this; // autofix - _ = name; // autofix - _ = from; // autofix - _ = source_index; // autofix - @panic(css.todo_stuff.depth); + const reference, const key = if (from.*) |specifier| switch (specifier) { + .global => return name[2..], + .file => |file| .{ + CssModuleReference{ .dependency = .{ .name = name[2..], .specifier = file } }, + file, + }, + .source_index => |dep_source_index| return this.config.pattern.writeToString( + allocator, + .{}, + this.hashes.items[dep_source_index], + this.sources.items[dep_source_index], + name[2..], + ), + } else { + // Local export. Mark as used. + const gop = this.exports_by_source_index.items[source_index].getOrPut(allocator, name) catch bun.outOfMemory(); + if (gop.found_existing) { + gop.value_ptr.is_referenced = true; + } else { + var res = ArrayList(u8){}; + res.appendSlice(allocator, "--") catch bun.outOfMemory(); + gop.value_ptr.* = CssModuleExport{ + .name = this.config.pattern.writeToString( + allocator, + res, + this.hashes.items[source_index], + this.sources.items[source_index], + name[2..], + ), + .composes = .{}, + .is_referenced = true, + }; + } + return null; + }; + + const the_hash = hash(allocator, "{s}_{s}_{s}", .{ this.hashes.items[source_index], name, key }, false); + + this.references.put( + allocator, + std.fmt.allocPrint(allocator, "--{s}", .{the_hash}) catch bun.outOfMemory(), + reference, + ) catch bun.outOfMemory(); + + return the_hash; } pub fn handleComposes( @@ -397,10 +451,33 @@ pub const CssModuleReference = union(enum) { // TODO: replace with bun's hash pub fn hash(allocator: Allocator, comptime fmt: []const u8, args: anytype, at_start: bool) []const u8 { - _ = fmt; // autofix - _ = args; // autofix - _ = allocator; // autofix - _ = at_start; // autofix - // @compileError(css.todo_stuff.depth); - @panic(css.todo_stuff.depth); + const count = std.fmt.count(fmt, args); + var stack_fallback = std.heap.stackFallback(128, allocator); + const fmt_alloc = if (count <= 128) stack_fallback.get() else allocator; + var hasher = bun.Wyhash11.init(0); + var fmt_str = std.fmt.allocPrint(fmt_alloc, fmt, args) catch bun.outOfMemory(); + hasher.update(fmt_str); + + const h: u32 = @truncate(hasher.final()); + var h_bytes: [4]u8 = undefined; + std.mem.writeInt(u32, &h_bytes, h, .little); + + const encode_len = bun.base64.encodeLen(h_bytes[0..]); + + var slice_to_write = if (encode_len <= 128 - @as(usize, @intFromBool(at_start))) + allocator.alloc(u8, encode_len + @as(usize, @intFromBool(at_start))) catch bun.outOfMemory() + else + fmt_str[0..]; + + const base64_encoded_hash_len = bun.base64.encode(slice_to_write, &h_bytes); + + const base64_encoded_hash = slice_to_write[0..base64_encoded_hash_len]; + + if (at_start and base64_encoded_hash.len > 0 and base64_encoded_hash[0] >= '0' and base64_encoded_hash[0] <= '9') { + std.mem.copyBackwards(u8, slice_to_write[1..][0..base64_encoded_hash_len], base64_encoded_hash); + slice_to_write[0] = '_'; + return slice_to_write[0 .. base64_encoded_hash_len + 1]; + } + + return base64_encoded_hash; } diff --git a/src/css/css_parser.zig b/src/css/css_parser.zig index ab434d3f0f09f6..40ceaa2966f33f 100644 --- a/src/css/css_parser.zig +++ b/src/css/css_parser.zig @@ -384,7 +384,7 @@ pub fn DefineShorthand(comptime T: type, comptime property_name: PropertyIdTag) // } // return null; - @panic(todo_stuff.depth); + @compileError(todo_stuff.depth); } /// Returns a shorthand from the longhand properties defined in the given declaration block. @@ -405,7 +405,7 @@ pub fn DefineShorthand(comptime T: type, comptime property_name: PropertyIdTag) // }; // return out; - @panic(todo_stuff.depth); + @compileError(todo_stuff.depth); } /// Returns a longhand property for this shorthand. @@ -430,7 +430,7 @@ pub fn DefineShorthand(comptime T: type, comptime property_name: PropertyIdTag) // } // } // return null; - @panic(todo_stuff.depth); + @compileError(todo_stuff.depth); } /// Updates this shorthand from a longhand property. @@ -451,7 +451,7 @@ pub fn DefineShorthand(comptime T: type, comptime property_name: PropertyIdTag) // } // } // return false; - @panic(todo_stuff.depth); + @compileError(todo_stuff.depth); } }; } @@ -916,12 +916,8 @@ pub fn DeriveValueType(comptime T: type) type { } fn consume_until_end_of_block(block_type: BlockType, tokenizer: *Tokenizer) void { - const StackCount = 16; - var sfb = std.heap.stackFallback(@sizeOf(BlockType) * StackCount, tokenizer.allocator); - const alloc = sfb.get(); - var stack = std.ArrayList(BlockType).initCapacity(alloc, StackCount) catch unreachable; - defer stack.deinit(); - + @setCold(true); + var stack = SmallList(BlockType, 16){}; stack.appendAssumeCapacity(block_type); while (switch (tokenizer.next()) { @@ -929,13 +925,13 @@ fn consume_until_end_of_block(block_type: BlockType, tokenizer: *Tokenizer) void .err => null, }) |tok| { if (BlockType.closing(&tok)) |b| { - if (stack.getLast() == b) { + if (stack.getLastUnchecked() == b) { _ = stack.pop(); - if (stack.items.len == 0) return; + if (stack.len() == 0) return; } } - if (BlockType.opening(&tok)) |bt| stack.append(bt) catch unreachable; + if (BlockType.opening(&tok)) |bt| stack.append(tokenizer.allocator, bt); } } @@ -4200,8 +4196,6 @@ const Tokenizer = struct { .position = 0, }; - // make current point to the first token - _ = lexer.next(); lexer.position = 0; return lexer; @@ -6253,6 +6247,20 @@ pub const serializer = struct { }; } else notation: { var buf: [129]u8 = undefined; + // We must pass finite numbers to dtoa_short + if (std.math.isPositiveInf(value)) { + const output = "1e999"; + try writer.writeAll(output); + return; + } else if (std.math.isNegativeInf(value)) { + const output = "-1e999"; + try writer.writeAll(output); + return; + } + // We shouldn't receive NaN here. + // NaN is not a valid CSS token and any inlined calculations from `calc()` we ensure + // are not NaN. + bun.debugAssert(!std.math.isNan(value)); const str, const notation = dtoa_short(&buf, value, 6); try writer.writeAll(str); break :notation notation; @@ -6691,6 +6699,7 @@ const Notation = struct { pub fn dtoa_short(buf: *[129]u8, value: f32, comptime precision: u8) struct { []u8, Notation } { buf[0] = '0'; + bun.debugAssert(std.math.isFinite(value)); const buf_len = bun.fmt.FormatDouble.dtoa(@ptrCast(buf[1..].ptr), @floatCast(value)).len; return restrict_prec(buf[0 .. buf_len + 1], precision); } diff --git a/src/css/properties/animation.zig b/src/css/properties/animation.zig index b6136db261854f..d6d8fb198f4820 100644 --- a/src/css/properties/animation.zig +++ b/src/css/properties/animation.zig @@ -47,9 +47,44 @@ pub const AnimationName = union(enum) { } pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void { - _ = this; // autofix - _ = dest; // autofix - @panic(css.todo_stuff.depth); + const css_module_animation_enabled = if (dest.css_module) |css_module| + css_module.config.animation + else + false; + + switch (this.*) { + .none => return dest.writeStr("none"), + .ident => |s| { + if (css_module_animation_enabled) { + if (dest.css_module) |*css_module| { + css_module.getReference(dest.allocator, s.v, dest.loc.source_index); + } + } + return s.toCssWithOptions(W, dest, css_module_animation_enabled); + }, + .string => |s| { + if (css_module_animation_enabled) { + if (dest.css_module) |*css_module| { + css_module.getReference(dest.allocator, s, dest.loc.source_index); + } + } + + // CSS-wide keywords and `none` cannot remove quotes + if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "none") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "initial") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "inherit") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "unset") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "default") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "revert") or + bun.strings.eqlCaseInsensitiveASCIIICheckLength(s, "revert-layer")) + { + css.serializer.serializeString(s, dest) catch return dest.addFmtError(); + return; + } + + return dest.writeIdent(s, css_module_animation_enabled); + }, + } } }; diff --git a/src/css/properties/css_modules.zig b/src/css/properties/css_modules.zig index fa087a3866df10..859ca91f577a25 100644 --- a/src/css/properties/css_modules.zig +++ b/src/css/properties/css_modules.zig @@ -40,8 +40,20 @@ pub const Composes = struct { loc: Location, pub fn parse(input: *css.Parser) css.Result(Composes) { - _ = input; // autofix - @panic(css.todo_stuff.depth); + const loc = input.currentSourceLocation(); + var names: CustomIdentList = .{}; + while (input.tryParse(parseOneIdent, .{}).asValue()) |name| { + names.append(input.allocator(), name); + } + + if (names.len() == 0) return .{ .err = input.newCustomError(css.ParserError{ .invalid_declaration = {} }) }; + + const from = if (input.tryParse(css.Parser.expectIdentMatching, .{"from"}).isOk()) switch (Specifier.parse(input)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + } else null; + + return .{ .result = Composes{ .names = names, .from = from, .loc = Location.fromSourceLocation(loc) } }; } pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { @@ -61,6 +73,17 @@ pub const Composes = struct { } } + fn parseOneIdent(input: *css.Parser) css.Result(CustomIdent) { + const name: CustomIdent = switch (CustomIdent.parse(input)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + if (bun.strings.eqlCaseInsensitiveASCII(name.v, "from", true)) return .{ .err = input.newErrorForNextToken() }; + + return .{ .result = name }; + } + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } diff --git a/src/css/properties/font.zig b/src/css/properties/font.zig index fe1980e19aa2d0..b5e8f12effca4e 100644 --- a/src/css/properties/font.zig +++ b/src/css/properties/font.zig @@ -160,17 +160,25 @@ pub const FontStretch = union(enum) { percentage: Percentage, // TODO: implement this - // pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveParse(@This()); + + pub fn toCss(this: *const FontStretch, comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { + if (dest.minify) { + const percentage: Percentage = this.intoPercentage(); + return percentage.toCss(W, dest); + } - pub fn parse(input: *css.Parser) css.Result(FontStretch) { - _ = input; // autofix - @panic(css.todo_stuff.depth); + return switch (this.*) { + .percentage => |*val| val.toCss(W, dest), + .keyword => |*kw| kw.toCss(W, dest), + }; } - pub fn toCss(this: *const FontStretch, comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { - _ = this; // autofix - _ = dest; // autofix - @panic(css.todo_stuff.depth); + pub fn intoPercentage(this: *const FontStretch) Percentage { + return switch (this.*) { + .percentage => |*val| val.*, + .keyword => |*kw| kw.intoPercentage(), + }; } pub fn eql(lhs: *const FontStretch, rhs: *const FontStretch) bool { @@ -215,6 +223,21 @@ pub const FontStretchKeyword = enum { pub inline fn default() FontStretchKeyword { return .normal; } + + pub fn intoPercentage(this: *const FontStretchKeyword) Percentage { + const val: f32 = switch (this.*) { + .@"ultra-condensed" => 0.5, + .@"extra-condensed" => 0.625, + .condensed => 0.75, + .@"semi-condensed" => 0.875, + .normal => 1.0, + .@"semi-expanded" => 1.125, + .expanded => 1.25, + .@"extra-expanded" => 1.5, + .@"ultra-expanded" => 2.0, + }; + return .{ .v = val }; + } }; /// A value for the [font-family](https://www.w3.org/TR/css-fonts-4/#font-family-prop) property. diff --git a/src/css/properties/transform.zig b/src/css/properties/transform.zig index 576779ad30c5c7..bcc9ebce9984cb 100644 --- a/src/css/properties/transform.zig +++ b/src/css/properties/transform.zig @@ -38,14 +38,62 @@ pub const TransformList = struct { v: ArrayList(Transform), pub fn parse(input: *css.Parser) Result(@This()) { - _ = input; // autofix - @panic(css.todo_stuff.depth); + if (input.tryParse(css.Parser.expectIdentMatching, .{"none"}).isOk()) { + return .{ .result = .{ .v = .{} } }; + } + + input.skipWhitespace(); + var results = ArrayList(Transform){}; + switch (Transform.parse(input)) { + .result => |first| results.append(input.allocator(), first) catch bun.outOfMemory(), + .err => |e| return .{ .err = e }, + } + + while (true) { + input.skipWhitespace(); + if (input.tryParse(Transform.parse, .{}).asValue()) |item| { + results.append(input.allocator(), item) catch bun.outOfMemory(); + } else { + return .{ .result = .{ .v = results } }; + } + } } pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { - _ = this; // autofix - _ = dest; // autofix - @panic(css.todo_stuff.depth); + if (this.v.items.len == 0) { + return dest.writeStr("none"); + } + + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + if (dest.minify) { + var base = ArrayList(u8){}; + const base_writer = base.writer(dest.allocator); + const WW = @TypeOf(base_writer); + + var scratchbuf = std.ArrayList(u8).init(dest.allocator); + defer scratchbuf.deinit(); + var p = Printer(WW).new( + dest.allocator, + scratchbuf, + base_writer, + css.PrinterOptions{ + .minify = true, + }, + dest.import_records, + ); + defer p.deinit(); + + try this.toCssBase(WW, &p); + + return dest.writeStr(base.items); + } + } + + fn toCssBase(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { + for (this.v.items) |*item| { + try item.toCss(W, dest); + } } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -145,14 +193,583 @@ pub const Transform = union(enum) { matrix_3d: Matrix3d(f32), pub fn parse(input: *css.Parser) Result(Transform) { - _ = input; // autofix - @panic(css.todo_stuff.depth); + const function = switch (input.expectFunction()) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + const Closure = struct { function: []const u8 }; + return input.parseNestedBlock( + Transform, + Closure{ .function = function }, + struct { + fn parse(closure: Closure, i: *css.Parser) css.Result(Transform) { + const location = i.currentSourceLocation(); + if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "matrix")) { + const a = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const b = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const c = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const d = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const e = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |ee| return .{ .err = ee }; + const f = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |ee| return .{ .err = ee }, + }; + return .{ .result = .{ .matrix = .{ .a = a, .b = b, .c = c, .d = d, .e = e, .f = f } } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "matrix3d")) { + const m11 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m12 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m13 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m14 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m21 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m22 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m23 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m24 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m31 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m32 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m33 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m34 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m41 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m42 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m43 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const m44 = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .matrix_3d = .{ + .m11 = m11, + .m12 = m12, + .m13 = m13, + .m14 = m14, + .m21 = m21, + .m22 = m22, + .m23 = m23, + .m24 = m24, + .m31 = m31, + .m32 = m32, + .m33 = m33, + .m34 = m34, + .m41 = m41, + .m42 = m42, + .m43 = m43, + .m44 = m44, + } } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "translate")) { + const x = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.tryParse(struct { + fn parse(p: *css.Parser) css.Result(void) { + return p.expectComma(); + } + }.parse, .{}).isOk()) { + const y = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .translate = .{ .x = x, .y = y } } }; + } else { + return .{ .result = .{ .translate = .{ .x = x, .y = LengthPercentage.zero() } } }; + } + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "translatex")) { + const x = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .translate_x = x } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "translatey")) { + const y = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .translate_y = y } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "translatez")) { + const z = switch (Length.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .translate_z = z } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "translate3d")) { + const x = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const y = switch (LengthPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const z = switch (Length.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .translate_3d = .{ .x = x, .y = y, .z = z } } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "scale")) { + const x = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.tryParse(struct { + fn parse(p: *css.Parser) css.Result(void) { + return p.expectComma(); + } + }.parse, .{}).isOk()) { + const y = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .scale = .{ .x = x, .y = y } } }; + } else { + return .{ .result = .{ .scale = .{ .x = x, .y = x.deepClone(i.allocator()) } } }; + } + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "scalex")) { + const x = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .scale_x = x } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "scaley")) { + const y = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .scale_y = y } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "scalez")) { + const z = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .scale_z = z } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "scale3d")) { + const x = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const y = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const z = switch (NumberOrPercentage.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .scale_3d = .{ .x = x, .y = y, .z = z } } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "rotate")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .rotate = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "rotatex")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .rotate_x = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "rotatey")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .rotate_y = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "rotatez")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .rotate_z = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "rotate3d")) { + const x = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const y = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const z = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .rotate_3d = .{ .x = x, .y = y, .z = z, .angle = angle } } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "skew")) { + const x = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.tryParse(struct { + fn parse(p: *css.Parser) css.Result(void) { + return p.expectComma(); + } + }.parse, .{}).isOk()) { + const y = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .skew = .{ .x = x, .y = y } } }; + } else { + return .{ .result = .{ .skew = .{ .x = x, .y = Angle{ .deg = 0.0 } } } }; + } + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "skewx")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .skew_x = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "skewy")) { + const angle = switch (Angle.parseWithUnitlessZero(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .skew_y = angle } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "perspective")) { + const len = switch (Length.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .perspective = len } }; + } else { + return .{ .err = location.newUnexpectedTokenError(.{ .ident = closure.function }) }; + } + } + }.parse, + ); } pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { - _ = this; // autofix - _ = dest; // autofix - @panic(css.todo_stuff.depth); + switch (this.*) { + .translate => |t| { + if (dest.minify and t.x.isZero() and !t.y.isZero()) { + try dest.writeStr("translateY("); + try t.y.toCss(W, dest); + } else { + try dest.writeStr("translate("); + try t.x.toCss(W, dest); + if (!t.y.isZero()) { + try dest.delim(',', false); + try t.y.toCss(W, dest); + } + } + try dest.writeChar(')'); + }, + .translate_x => |x| { + try dest.writeStr(if (dest.minify) "translate(" else "translateX("); + try x.toCss(W, dest); + try dest.writeChar(')'); + }, + .translate_y => |y| { + try dest.writeStr("translateY("); + try y.toCss(W, dest); + try dest.writeChar(')'); + }, + .translate_z => |z| { + try dest.writeStr("translateZ("); + try z.toCss(W, dest); + try dest.writeChar(')'); + }, + .translate_3d => |t| { + if (dest.minify and !t.x.isZero() and t.y.isZero() and t.z.isZero()) { + try dest.writeStr("translate("); + try t.x.toCss(W, dest); + } else if (dest.minify and t.x.isZero() and !t.y.isZero() and t.z.isZero()) { + try dest.writeStr("translateY("); + try t.y.toCss(W, dest); + } else if (dest.minify and t.x.isZero() and t.y.isZero() and !t.z.isZero()) { + try dest.writeStr("translateZ("); + try t.z.toCss(W, dest); + } else if (dest.minify and t.z.isZero()) { + try dest.writeStr("translate("); + try t.x.toCss(W, dest); + try dest.delim(',', false); + try t.y.toCss(W, dest); + } else { + try dest.writeStr("translate3d("); + try t.x.toCss(W, dest); + try dest.delim(',', false); + try t.y.toCss(W, dest); + try dest.delim(',', false); + try t.z.toCss(W, dest); + } + try dest.writeChar(')'); + }, + .scale => |s| { + const x: f32 = s.x.intoF32(); + const y: f32 = s.y.intoF32(); + if (dest.minify and x == 1.0 and y != 1.0) { + try dest.writeStr("scaleY("); + try css.CSSNumberFns.toCss(&y, W, dest); + } else if (dest.minify and x != 1.0 and y == 1.0) { + try dest.writeStr("scaleX("); + try css.CSSNumberFns.toCss(&x, W, dest); + } else { + try dest.writeStr("scale("); + try css.CSSNumberFns.toCss(&x, W, dest); + if (y != x) { + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&y, W, dest); + } + } + try dest.writeChar(')'); + }, + .scale_x => |x| { + try dest.writeStr("scaleX("); + try css.CSSNumberFns.toCss(&x.intoF32(), W, dest); + try dest.writeChar(')'); + }, + .scale_y => |y| { + try dest.writeStr("scaleY("); + try css.CSSNumberFns.toCss(&y.intoF32(), W, dest); + try dest.writeChar(')'); + }, + .scale_z => |z| { + try dest.writeStr("scaleZ("); + try css.CSSNumberFns.toCss(&z.intoF32(), W, dest); + try dest.writeChar(')'); + }, + .scale_3d => |s| { + const x: f32 = s.x.intoF32(); + const y: f32 = s.y.intoF32(); + const z: f32 = s.z.intoF32(); + if (dest.minify and z == 1.0 and x == y) { + try dest.writeStr("scale("); + try css.CSSNumberFns.toCss(&x, W, dest); + } else if (dest.minify and x != 1.0 and y == 1.0 and z == 1.0) { + try dest.writeStr("scaleX("); + try css.CSSNumberFns.toCss(&x, W, dest); + } else if (dest.minify and x == 1.0 and y != 1.0 and z == 1.0) { + try dest.writeStr("scaleY("); + try css.CSSNumberFns.toCss(&y, W, dest); + } else if (dest.minify and x == 1.0 and y == 1.0 and z != 1.0) { + try dest.writeStr("scaleZ("); + try css.CSSNumberFns.toCss(&z, W, dest); + } else if (dest.minify and z == 1.0) { + try dest.writeStr("scale("); + try css.CSSNumberFns.toCss(&x, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&y, W, dest); + } else { + try dest.writeStr("scale3d("); + try css.CSSNumberFns.toCss(&x, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&y, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&z, W, dest); + } + try dest.writeChar(')'); + }, + .rotate => |angle| { + try dest.writeStr("rotate("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .rotate_x => |angle| { + try dest.writeStr("rotateX("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .rotate_y => |angle| { + try dest.writeStr("rotateY("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .rotate_z => |angle| { + try dest.writeStr(if (dest.minify) "rotate(" else "rotateZ("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .rotate_3d => |r| { + if (dest.minify and r.x == 1.0 and r.y == 0.0 and r.z == 0.0) { + try dest.writeStr("rotateX("); + try r.angle.toCssWithUnitlessZero(W, dest); + } else if (dest.minify and r.x == 0.0 and r.y == 1.0 and r.z == 0.0) { + try dest.writeStr("rotateY("); + try r.angle.toCssWithUnitlessZero(W, dest); + } else if (dest.minify and r.x == 0.0 and r.y == 0.0 and r.z == 1.0) { + try dest.writeStr("rotate("); + try r.angle.toCssWithUnitlessZero(W, dest); + } else { + try dest.writeStr("rotate3d("); + try css.CSSNumberFns.toCss(&r.x, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&r.y, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&r.z, W, dest); + try dest.delim(',', false); + try r.angle.toCssWithUnitlessZero(W, dest); + } + try dest.writeChar(')'); + }, + .skew => |s| { + if (dest.minify and s.x.isZero() and !s.y.isZero()) { + try dest.writeStr("skewY("); + try s.y.toCssWithUnitlessZero(W, dest); + } else { + try dest.writeStr("skew("); + try s.x.toCss(W, dest); + if (!s.y.isZero()) { + try dest.delim(',', false); + try s.y.toCssWithUnitlessZero(W, dest); + } + } + try dest.writeChar(')'); + }, + .skew_x => |angle| { + try dest.writeStr(if (dest.minify) "skew(" else "skewX("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .skew_y => |angle| { + try dest.writeStr("skewY("); + try angle.toCssWithUnitlessZero(W, dest); + try dest.writeChar(')'); + }, + .perspective => |len| { + try dest.writeStr("perspective("); + try len.toCss(W, dest); + try dest.writeChar(')'); + }, + .matrix => |m| { + try dest.writeStr("matrix("); + try css.CSSNumberFns.toCss(&m.a, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.b, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.c, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.d, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.e, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.f, W, dest); + try dest.writeChar(')'); + }, + .matrix_3d => |m| { + try dest.writeStr("matrix3d("); + try css.CSSNumberFns.toCss(&m.m11, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m12, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m13, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m14, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m21, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m22, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m23, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m24, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m31, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m32, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m33, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m34, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m41, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m42, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m43, W, dest); + try dest.delim(',', false); + try css.CSSNumberFns.toCss(&m.m44, W, dest); + try dest.writeChar(')'); + }, + } } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { diff --git a/src/css/rules/keyframes.zig b/src/css/rules/keyframes.zig index 640683d41a88ca..d79eca6a7ab54b 100644 --- a/src/css/rules/keyframes.zig +++ b/src/css/rules/keyframes.zig @@ -306,7 +306,7 @@ pub const KeyframesRule = struct { pub fn getFallbacks(this: *This, comptime T: type, targets: *const css.targets.Targets) []css.CssRule(T) { _ = this; // autofix _ = targets; // autofix - @panic(css.todo_stuff.depth); + @compileError(css.todo_stuff.depth); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) This { diff --git a/src/css/rules/property.zig b/src/css/rules/property.zig index b3044d183657c8..8ad0892f10c147 100644 --- a/src/css/rules/property.zig +++ b/src/css/rules/property.zig @@ -123,7 +123,7 @@ pub const PropertyRule = struct { dest.dedent(); try dest.newline(); - try dest.writeChar(';'); + try dest.writeChar('}'); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) This { diff --git a/src/css/rules/supports.zig b/src/css/rules/supports.zig index a07a78494ebfdb..126720002bd301 100644 --- a/src/css/rules/supports.zig +++ b/src/css/rules/supports.zig @@ -59,6 +59,21 @@ pub const SupportsCondition = union(enum) { /// An unknown condition. unknown: []const u8, + pub fn deinit(this: *@This(), allocator: std.mem.Allocator) void { + switch (this.*) { + .not => |not| { + not.deinit(allocator); + allocator.destroy(not); + }, + inline .@"and", .@"or" => |*list| { + css.deepDeinit(SupportsCondition, allocator, list); + }, + .declaration => {}, + .selector => {}, + .unknown => {}, + } + } + pub fn eql(this: *const SupportsCondition, other: *const SupportsCondition) bool { return css.implementEql(SupportsCondition, this, other); } diff --git a/src/css/selectors/parser.zig b/src/css/selectors/parser.zig index a89dd6345a68bf..c545c826b9a9b4 100644 --- a/src/css/selectors/parser.zig +++ b/src/css/selectors/parser.zig @@ -1050,9 +1050,7 @@ pub const SelectorParser = struct { pub fn parseFunctionalPseudoElement(this: *SelectorParser, name: []const u8, input: *css.Parser) Result(Impl.SelectorImpl.PseudoElement) { _ = this; // autofix - _ = name; // autofix - _ = input; // autofix - @panic(css.todo_stuff.depth); + return .{ .err = input.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.{ .unsupported_pseudo_class_or_element = name })) }; } fn parseIsAndWhere(this: *const SelectorParser) bool { diff --git a/src/css/small_list.zig b/src/css/small_list.zig index fbc86efdd03e8d..bb48ca8593953a 100644 --- a/src/css/small_list.zig +++ b/src/css/small_list.zig @@ -87,6 +87,11 @@ pub fn SmallList(comptime T: type, comptime N: comptime_int) type { return ret; } + pub inline fn getLastUnchecked(this: *const @This()) T { + if (this.spilled()) return this.data.heap.ptr[this.data.heap.len - 1]; + return this.data.inlined[this.capacity - 1]; + } + pub inline fn at(this: *const @This(), idx: u32) *const T { return &this.as_const_ptr()[idx]; } @@ -173,6 +178,7 @@ pub fn SmallList(comptime T: type, comptime N: comptime_int) type { pub inline fn helper(comptime prefix: []const u8, pfs: *css.VendorPrefix, pfi: *const SmallList(T, 1), r: *bun.BabyList(This), alloc: Allocator) void { if (pfs.contains(css.VendorPrefix.fromName(prefix))) { var images = SmallList(T, 1).initCapacity(alloc, pfi.len()); + images.setLen(pfi.len()); for (images.slice_mut(), pfi.slice()) |*out, *in| { const image = in.getImage().getPrefixed(alloc, css.VendorPrefix.fromName(prefix)); out.* = in.withImage(alloc, image); @@ -431,6 +437,14 @@ pub fn SmallList(comptime T: type, comptime N: comptime_int) type { len_ptr.* += 1; } + pub fn pop(this: *@This()) ?T { + const ptr, const len_ptr, _ = this.tripleMut(); + if (len_ptr.* == 0) return null; + const last_index = len_ptr.* - 1; + len_ptr.* = last_index; + return ptr[last_index]; + } + pub fn append(this: *@This(), allocator: Allocator, item: T) void { var ptr, var len_ptr, const capp = this.tripleMut(); if (len_ptr.* == capp) { diff --git a/src/css/values/easing.zig b/src/css/values/easing.zig index 4d7c7cd00c9b1f..b5e5ea51c2a3e7 100644 --- a/src/css/values/easing.zig +++ b/src/css/values/easing.zig @@ -228,7 +228,7 @@ pub const StepPosition = enum { pub fn toCss(this: *const StepPosition, comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { _ = this; // autofix _ = dest; // autofix - @panic(css.todo_stuff.depth); + @compileError(css.todo_stuff.depth); } pub fn parse(input: *css.Parser) Result(StepPosition) { diff --git a/src/css/values/gradient.zig b/src/css/values/gradient.zig index 292db6ec8814cd..0910a65a780c19 100644 --- a/src/css/values/gradient.zig +++ b/src/css/values/gradient.zig @@ -49,105 +49,146 @@ pub const Gradient = union(enum) { closure: Closure, input_: *css.Parser, ) Result(Gradient) { - // css.todo_stuff.match_ignore_ascii_case - if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "linear-gradient")) { - return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .none = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "repeating-linear-gradient")) { - return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .none = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "radial-gradient")) { - return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .none = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "repeating-radial-gradient")) { - return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .none = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "conic-gradient")) { - return .{ .result = .{ .conic = switch (ConicGradient.parse(input_)) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "repeating-conic-gradient")) { - return .{ .result = .{ .repeating_conic = switch (ConicGradient.parse(input_)) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-webkit-linear-gradient")) { - return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-webkit-repeating-linear-gradient")) { - return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-webkit-radial-gradient")) { - return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-webkit-repeating-radial-gradient")) { - return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-moz-linear-gradient")) { - return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .moz = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-moz-repeating-linear-gradient")) { - return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .moz = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-moz-radial-gradient")) { - return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .moz = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-moz-repeating-radial-gradient")) { - return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .moz = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-o-linear-gradient")) { - return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .o = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-o-repeating-linear-gradient")) { - return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .o = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-o-radial-gradient")) { - return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .o = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-o-repeating-radial-gradient")) { - return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .o = true })) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.func, "-webkit-gradient")) { - return .{ .result = .{ .@"webkit-gradient" = switch (WebKitGradient.parse(input_)) { - .result => |vv| vv, - .err => |e| return .{ .err = e }, - } } }; - } else { + const Map = comptime bun.ComptimeEnumMap(enum { + @"linear-gradient", + @"repeating-linear-gradient", + @"radial-gradient", + @"repeating-radial-gradient", + @"conic-gradient", + @"repeating-conic-gradient", + @"-webkit-linear-gradient", + @"-webkit-repeating-linear-gradient", + @"-webkit-radial-gradient", + @"-webkit-repeating-radial-gradient", + @"-moz-linear-gradient", + @"-moz-repeating-linear-gradient", + @"-moz-radial-gradient", + @"-moz-repeating-radial-gradient", + @"-o-linear-gradient", + @"-o-repeating-linear-gradient", + @"-o-radial-gradient", + @"-o-repeating-radial-gradient", + @"-webkit-gradient", + }); + if (Map.getAnyCase(closure.func)) |matched| + switch (matched) { + .@"linear-gradient" => { + return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .none = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"repeating-linear-gradient" => { + return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .none = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"radial-gradient" => { + return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .none = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"repeating-radial-gradient" => { + return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .none = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"conic-gradient" => { + return .{ .result = .{ .conic = switch (ConicGradient.parse(input_)) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"repeating-conic-gradient" => { + return .{ .result = .{ .repeating_conic = switch (ConicGradient.parse(input_)) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-webkit-linear-gradient" => { + return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-webkit-repeating-linear-gradient" => { + return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-webkit-radial-gradient" => { + return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-webkit-repeating-radial-gradient" => { + return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .webkit = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-moz-linear-gradient" => { + return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .moz = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-moz-repeating-linear-gradient" => { + return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .moz = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-moz-radial-gradient" => { + return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .moz = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-moz-repeating-radial-gradient" => { + return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .moz = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-o-linear-gradient" => { + return .{ .result = .{ .linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .o = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-o-repeating-linear-gradient" => { + return .{ .result = .{ .repeating_linear = switch (LinearGradient.parse(input_, css.VendorPrefix{ .o = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-o-radial-gradient" => { + return .{ .result = .{ .radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .o = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-o-repeating-radial-gradient" => { + return .{ .result = .{ .repeating_radial = switch (RadialGradient.parse(input_, css.VendorPrefix{ .o = true })) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + .@"-webkit-gradient" => { + return .{ .result = .{ .@"webkit-gradient" = switch (WebKitGradient.parse(input_)) { + .result => |vv| vv, + .err => |e| return .{ .err = e }, + } } }; + }, + } + else return .{ .err = closure.location.newUnexpectedTokenError(.{ .ident = closure.func }) }; - } } }.parse); } @@ -157,7 +198,7 @@ pub const Gradient = union(enum) { .linear => |g| .{ "linear-gradient(", g.vendor_prefix }, .repeating_linear => |g| .{ "repeating-linear-gradient(", g.vendor_prefix }, .radial => |g| .{ "radial-gradient(", g.vendor_prefix }, - .repeating_radial => |g| .{ "repeating-linear-gradient(", g.vendor_prefix }, + .repeating_radial => |g| .{ "repeating-radial-gradient(", g.vendor_prefix }, .conic => .{ "conic-gradient(", null }, .repeating_conic => .{ "repeating-conic-gradient(", null }, .@"webkit-gradient" => .{ "-webkit-gradient(", null }, @@ -171,7 +212,7 @@ pub const Gradient = union(enum) { switch (this.*) { .linear, .repeating_linear => |*linear| { - try linear.toCss(W, dest, linear.vendor_prefix.eq(css.VendorPrefix{ .none = true })); + try linear.toCss(W, dest, linear.vendor_prefix.neq(css.VendorPrefix{ .none = true })); }, .radial, .repeating_radial => |*radial| { try radial.toCss(W, dest); @@ -475,7 +516,7 @@ pub const RadialGradient = struct { } pub fn toCss(this: *const RadialGradient, comptime W: type, dest: *Printer(W)) PrintErr!void { - if (std.meta.eql(this.shape, EndingShape.default())) { + if (!std.meta.eql(this.shape, EndingShape.default())) { try this.shape.toCss(W, dest); if (this.position.isCenter()) { try dest.delim(',', false); @@ -544,16 +585,16 @@ pub const ConicGradient = struct { // https://w3c.github.io/csswg-drafts/css-images-4/#valdef-conic-gradient-angle return Angle.parseWithUnitlessZero(i); } - }.parse, .{}).unwrapOr(Angle{ .deg = 0.0 }); + }.parse, .{}); const position = input.tryParse(struct { inline fn parse(i: *css.Parser) Result(Position) { if (i.expectIdentMatching("at").asErr()) |e| return .{ .err = e }; return Position.parse(i); } - }.parse, .{}).unwrapOr(Position.center()); + }.parse, .{}); - if (!angle.eql(&Angle{ .deg = 0.0 }) or !std.meta.eql(position, Position.center())) { + if (angle.isOk() or position.isOk()) { if (input.expectComma().asErr()) |e| return .{ .err = e }; } @@ -562,8 +603,8 @@ pub const ConicGradient = struct { .err => |e| return .{ .err = e }, }; return .{ .result = ConicGradient{ - .angle = angle, - .position = position, + .angle = angle.unwrapOr(Angle{ .deg = 0.0 }), + .position = position.unwrapOr(Position.center()), .items = items, } }; } @@ -1346,7 +1387,7 @@ pub fn ColorStop(comptime D: type) type { pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void { try this.color.toCss(W, dest); if (this.position) |*position| { - try dest.delim(',', false); + try dest.writeChar(' '); try css.generic.toCss(D, position, W, dest); } return; diff --git a/src/css/values/ident.zig b/src/css/values/ident.zig index ee861540c98eda..9dab1de5be444e 100644 --- a/src/css/values/ident.zig +++ b/src/css/values/ident.zig @@ -49,7 +49,7 @@ pub const DashedIdentReference = struct { pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { if (dest.css_module) |*css_module| { if (css_module.config.dashed_idents) { - if (css_module.referenceDashed(this.ident.v, &this.from, dest.loc.source_index)) |name| { + if (css_module.referenceDashed(dest.allocator, this.ident.v, &this.from, dest.loc.source_index)) |name| { try dest.writeStr("--"); css.serializer.serializeName(name, dest) catch return dest.addFmtError(); return; diff --git a/src/css/values/percentage.zig b/src/css/values/percentage.zig index bdafae93e6420d..081206f7d0bf01 100644 --- a/src/css/values/percentage.zig +++ b/src/css/values/percentage.zig @@ -479,6 +479,10 @@ pub const NumberOrPercentage = union(enum) { // @panic(css.todo_stuff.depth); // } + pub fn deepClone(this: *const NumberOrPercentage, allocator: std.mem.Allocator) NumberOrPercentage { + return css.implementDeepClone(@This(), this, allocator); + } + pub fn eql(this: *const NumberOrPercentage, other: *const NumberOrPercentage) bool { return switch (this.*) { .number => |*a| switch (other.*) { diff --git a/src/css/values/syntax.zig b/src/css/values/syntax.zig index 5f8a74336709bd..ee46b209dae436 100644 --- a/src/css/values/syntax.zig +++ b/src/css/values/syntax.zig @@ -88,7 +88,7 @@ pub const SyntaxString = union(enum) { // PERF(alloc): count first? while (true) { - const component = switch (SyntaxComponent.parseString(trimmed_input)) { + const component = switch (SyntaxComponent.parseString(&trimmed_input)) { .result => |v| v, .err => |e| return .{ .err = e }, }; @@ -260,8 +260,7 @@ pub const SyntaxComponent = struct { kind: SyntaxComponentKind, multiplier: Multiplier, - pub fn parseString(input_: []const u8) css.Maybe(SyntaxComponent, void) { - var input = input_; + pub fn parseString(input: *[]const u8) css.Maybe(SyntaxComponent, void) { const kind = switch (SyntaxComponentKind.parseString(input)) { .result => |vv| vv, .err => |e| return .{ .err = e }, @@ -276,11 +275,11 @@ pub const SyntaxComponent = struct { } var multiplier: Multiplier = .none; - if (bun.strings.startsWithChar(input, '+')) { - input = input[1..]; + if (bun.strings.startsWithChar(input.*, '+')) { + input.* = input.*[1..]; multiplier = .space; - } else if (bun.strings.startsWithChar(input, '#')) { - input = input[1..]; + } else if (bun.strings.startsWithChar(input.*, '#')) { + input.* = input.*[1..]; multiplier = .comma; } @@ -334,13 +333,13 @@ pub const SyntaxComponentKind = union(enum) { /// A literal component. literal: []const u8, - pub fn parseString(input_: []const u8) css.Maybe(SyntaxComponentKind, void) { + pub fn parseString(input: *[]const u8) css.Maybe(SyntaxComponentKind, void) { // https://drafts.css-houdini.org/css-properties-values-api/#consume-syntax-component - var input = std.mem.trimLeft(u8, input_, SPACE_CHARACTERS); - if (bun.strings.startsWithChar(input, '<')) { + input.* = std.mem.trimLeft(u8, input.*, SPACE_CHARACTERS); + if (bun.strings.startsWithChar(input.*, '<')) { // https://drafts.css-houdini.org/css-properties-values-api/#consume-data-type-name - const end_idx = std.mem.indexOfScalar(u8, input, '>') orelse return .{ .err = {} }; - const name = input[1..end_idx]; + const end_idx = std.mem.indexOfScalar(u8, input.*, '>') orelse return .{ .err = {} }; + const name = input.*[1..end_idx]; // todo_stuff.match_ignore_ascii_case const component: SyntaxComponentKind = if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "length")) .length @@ -373,17 +372,17 @@ pub const SyntaxComponentKind = union(enum) { else return .{ .err = {} }; - input = input[end_idx + 1 ..]; + input.* = input.*[end_idx + 1 ..]; return .{ .result = component }; - } else if (input.len > 0 and isIdentStart(input[0])) { + } else if (input.len > 0 and isIdentStart(input.*[0])) { // A literal. var end_idx: usize = 0; while (end_idx < input.len and - isNameCodePoint(input[end_idx])) : (end_idx += - bun.strings.utf8ByteSequenceLengthUnsafe(input[end_idx])) + isNameCodePoint(input.*[end_idx])) : (end_idx += + bun.strings.utf8ByteSequenceLengthUnsafe(input.*[end_idx])) {} - const literal = input[0..end_idx]; - input = input[end_idx..]; + const literal = input.*[0..end_idx]; + input.* = input.*[end_idx..]; return .{ .result = SyntaxComponentKind{ .literal = literal } }; } else { return .{ .err = {} }; diff --git a/test/js/bun/css/css-fuzz.test.ts b/test/js/bun/css/css-fuzz.test.ts index 6a46bfc16b4540..668847af94c754 100644 --- a/test/js/bun/css/css-fuzz.test.ts +++ b/test/js/bun/css/css-fuzz.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "bun:test"; -import { isCI } from "harness"; +import { isCI, isDebug } from "harness"; interface InvalidFuzzOptions { maxLength: number; @@ -7,6 +7,9 @@ interface InvalidFuzzOptions { iterations: number; } +const shutup = process.env.CSS_FUZZ_SHUTUP === "1"; +const log = shutup ? () => {} : console.log; + // Collection of invalid CSS generation strategies const invalidGenerators = { // Syntax errors @@ -62,7 +65,7 @@ const invalidGenerators = { // Memory and resource stress memory: { - deepNesting: (depth: number = 1000) => { + deepNesting: (depth: number = 300) => { let css = ""; for (let i = 0; i < depth; i++) { css += "@media screen {"; @@ -111,107 +114,108 @@ function corruptCSS(css: string): string { // TODO: if (!isCI) { // Main fuzzing test suite for invalid inputs - test.each([ - ["syntax", 1000], - ["structure", 1000], - ["encoding", 500], - ["memory", 100], - ])("CSS Parser Invalid Input Fuzzing - %s (%d iterations)", async (strategy, iterations) => { - const options: InvalidFuzzOptions = { - maxLength: 10000, - strategy: strategy as any, - iterations, - }; - - let crashCount = 0; - let errorCount = 0; - const startTime = performance.now(); - - for (let i = 0; i < options.iterations; i++) { - let invalidCSS = ""; - - switch (strategy) { - case "syntax": - invalidCSS = - invalidGenerators.syntax[ - Object.keys(invalidGenerators.syntax)[ - Math.floor(Math.random() * Object.keys(invalidGenerators.syntax).length) - ] - ]()[Math.floor(Math.random() * 5)]; - break; - - case "structure": - invalidCSS = - invalidGenerators.structure[ - Object.keys(invalidGenerators.structure)[ - Math.floor(Math.random() * Object.keys(invalidGenerators.structure).length) - ] - ]()[Math.floor(Math.random() * 3)]; - break; - - case "encoding": - invalidCSS = - invalidGenerators.encoding[ - Object.keys(invalidGenerators.encoding)[ - Math.floor(Math.random() * Object.keys(invalidGenerators.encoding).length) - ] - ]()[0]; - break; - - case "memory": - const memoryFuncs = Object.keys(invalidGenerators.memory); - const selectedFunc = memoryFuncs[Math.floor(Math.random() * memoryFuncs.length)]; - invalidCSS = invalidGenerators.memory[selectedFunc](1000); - break; - } + test.each( + [["syntax", 1000], ["structure", 1000], ["encoding", 500], !isDebug ? ["memory", 100] : []].filter( + xs => xs.length > 0, + ), + )( + "CSS Parser Invalid Input Fuzzing - %s (%d iterations)", + async (strategy, iterations) => { + const options: InvalidFuzzOptions = { + maxLength: 10000, + strategy: strategy as any, + iterations, + }; - // Further corrupt the CSS randomly - if (Math.random() < 0.3) { - invalidCSS = corruptCSS(invalidCSS); - } + let crashCount = 0; + let errorCount = 0; + const startTime = performance.now(); - console.log("--- CSS Fuzz ---"); - invalidCSS = invalidCSS + ""; - console.log(JSON.stringify(invalidCSS, null, 2)); - await Bun.write("invalid.css", invalidCSS); + for (let i = 0; i < options.iterations; i++) { + let invalidCSS = ""; - try { - const result = await Bun.build({ - entrypoints: ["invalid.css"], - experimentalCss: true, - }); + switch (strategy) { + case "syntax": + invalidCSS = + invalidGenerators.syntax[ + Object.keys(invalidGenerators.syntax)[ + Math.floor(Math.random() * Object.keys(invalidGenerators.syntax).length) + ] + ]()[Math.floor(Math.random() * 5)]; + break; + + case "structure": + invalidCSS = + invalidGenerators.structure[ + Object.keys(invalidGenerators.structure)[ + Math.floor(Math.random() * Object.keys(invalidGenerators.structure).length) + ] + ]()[Math.floor(Math.random() * 3)]; + break; - if (result.logs.length > 0) { - throw new AggregateError("CSS parser returned logs", result.logs); + case "encoding": + invalidCSS = + invalidGenerators.encoding[ + Object.keys(invalidGenerators.encoding)[ + Math.floor(Math.random() * Object.keys(invalidGenerators.encoding).length) + ] + ]()[0]; + break; + + case "memory": + const memoryFuncs = Object.keys(invalidGenerators.memory); + const selectedFunc = memoryFuncs[Math.floor(Math.random() * memoryFuncs.length)]; + invalidCSS = invalidGenerators.memory[selectedFunc](); + break; } - // We expect the parser to either throw an error or return a valid result - // If it returns undefined/null, that's a potential issue - if (result === undefined || result === null) { - crashCount++; - console.error(`Parser returned ${result} for input:\n${invalidCSS.slice(0, 100)}...`); + // Further corrupt the CSS randomly + if (Math.random() < 0.3) { + invalidCSS = corruptCSS(invalidCSS); } - } catch (error) { - // Expected behavior for invalid CSS - errorCount++; - // Check for specific error types we want to track - if (error instanceof RangeError || error instanceof TypeError) { - console.warn(`Unexpected error type: ${error.constructor.name} for input:\n${invalidCSS.slice(0, 100)}...`); + log("--- CSS Fuzz ---"); + invalidCSS = invalidCSS + ""; + log(JSON.stringify(invalidCSS, null, 2)); + await Bun.write("invalid.css", invalidCSS); + + try { + const result = await Bun.build({ + entrypoints: ["invalid.css"], + experimentalCss: true, + }); + + if (result.logs.length > 0) { + throw new AggregateError("CSS parser returned logs", result.logs); + } + + // We expect the parser to either throw an error or return a valid result + // If it returns undefined/null, that's a potential issue + if (result === undefined || result === null) { + crashCount++; + console.error(`Parser returned ${result} for input:\n${invalidCSS.slice(0, 100)}...`); + } + } catch (error) { + // Expected behavior for invalid CSS + errorCount++; + + // Check for specific error types we want to track + if (error instanceof RangeError || error instanceof TypeError) { + console.warn(`Unexpected error type: ${error.constructor.name} for input:\n${invalidCSS.slice(0, 100)}...`); + } } - } - // Memory check every 100 iterations - if (i % 100 === 0) { - const heapUsed = process.memoryUsage().heapUsed / 1024 / 1024; - expect(heapUsed).toBeLessThan(500); // Alert if memory usage exceeds 500MB + // Memory check every 100 iterations + if (i % 100 === 0) { + const heapUsed = process.memoryUsage().heapUsed / 1024 / 1024; + expect(heapUsed).toBeLessThan(500); // Alert if memory usage exceeds 500MB + } } - } - const endTime = performance.now(); - const duration = endTime - startTime; + const endTime = performance.now(); + const duration = endTime - startTime; - console.log(` + console.log(` Strategy: ${strategy} Total iterations: ${iterations} Crashes: ${crashCount} @@ -220,10 +224,12 @@ if (!isCI) { Average time per test: ${(duration / iterations).toFixed(2)}ms `); - // We expect some errors for invalid input, but no crashes - expect(crashCount).toBe(0); - expect(errorCount).toBeGreaterThan(0); - }); + // We expect some errors for invalid input, but no crashes + expect(crashCount).toBe(0); + expect(errorCount).toBeGreaterThan(0); + }, + 10 * 1000, + ); // Additional test for mixed valid/invalid input test("CSS Parser Mixed Input Fuzzing", async () => { diff --git a/test/js/bun/css/css.test.ts b/test/js/bun/css/css.test.ts index 842160c315ebf8..da634ba716242b 100644 --- a/test/js/bun/css/css.test.ts +++ b/test/js/bun/css/css.test.ts @@ -5,9 +5,19 @@ import { describe, expect, test } from "bun:test"; import "harness"; import path from "path"; -import { attrTest, cssTest, indoc, indoc, minify_test, minifyTest, prefix_test } from "./util"; +import { attrTest, cssTest, indoc, minify_test, minifyTest, prefix_test } from "./util"; describe("css tests", () => { + test("edge case", () => { + minifyTest( + // Problem: the value is being printed as Infinity in our restrict_prec thing but the internal thing actually wants it as 3.40282e38px + `.rounded-full { + border-radius: calc(infinity * 1px); + width: calc(infinity * -1px); +}`, + indoc`.rounded-full{border-radius:1e999px;width:-1e999px}`, + ); + }); describe("border_spacing", () => { minifyTest( ` @@ -3254,4 +3264,272 @@ describe("css tests", () => { }, ); }); + + describe("linear-gradient", () => { + minifyTest(".foo { background: linear-gradient(yellow, blue) }", ".foo{background:linear-gradient(#ff0,#00f)}"); + minifyTest( + ".foo { background: linear-gradient(to bottom, yellow, blue); }", + ".foo{background:linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(180deg, yellow, blue); }", + ".foo{background:linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(0.5turn, yellow, blue); }", + ".foo{background:linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow 10%, blue 20%) }", + ".foo{background:linear-gradient(#ff0 10%,#00f 20%)}", + ); + minifyTest( + ".foo { background: linear-gradient(to top, blue, yellow); }", + ".foo{background:linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(to top, blue 10%, yellow 20%); }", + ".foo{background:linear-gradient(#ff0 80%,#00f 90%)}", + ); + minifyTest( + ".foo { background: linear-gradient(to top, blue 10px, yellow 20px); }", + ".foo{background:linear-gradient(0deg,#00f 10px,#ff0 20px)}", + ); + minifyTest( + ".foo { background: linear-gradient(135deg, yellow, blue); }", + ".foo{background:linear-gradient(135deg,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, blue 20%, #0f0); }", + ".foo{background:linear-gradient(#ff0,#00f 20%,#0f0)}", + ); + minifyTest( + ".foo { background: linear-gradient(to top right, red, white, blue) }", + ".foo{background:linear-gradient(to top right,red,#fff,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, blue calc(10% * 2), #0f0); }", + ".foo{background:linear-gradient(#ff0,#00f 20%,#0f0)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, 20%, blue); }", + ".foo{background:linear-gradient(#ff0,20%,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, 50%, blue); }", + ".foo{background:linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, 20px, blue); }", + ".foo{background:linear-gradient(#ff0,20px,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, 50px, blue); }", + ".foo{background:linear-gradient(#ff0,50px,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, 50px, blue); }", + ".foo{background:linear-gradient(#ff0,50px,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, red 30% 40%, blue); }", + ".foo{background:linear-gradient(#ff0,red 30% 40%,#00f)}", + ); + minifyTest( + ".foo { background: linear-gradient(yellow, red 30%, red 40%, blue); }", + ".foo{background:linear-gradient(#ff0,red 30% 40%,#00f)}", + ); + minifyTest(".foo { background: linear-gradient(0, yellow, blue); }", ".foo{background:linear-gradient(#00f,#ff0)}"); + minifyTest( + ".foo { background: -webkit-linear-gradient(yellow, blue) }", + ".foo{background:-webkit-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-linear-gradient(bottom, yellow, blue); }", + ".foo{background:-webkit-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-linear-gradient(top right, red, white, blue) }", + ".foo{background:-webkit-linear-gradient(top right,red,#fff,#00f)}", + ); + minifyTest( + ".foo { background: -moz-linear-gradient(yellow, blue) }", + ".foo{background:-moz-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -moz-linear-gradient(bottom, yellow, blue); }", + ".foo{background:-moz-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -moz-linear-gradient(top right, red, white, blue) }", + ".foo{background:-moz-linear-gradient(top right,red,#fff,#00f)}", + ); + minifyTest( + ".foo { background: -o-linear-gradient(yellow, blue) }", + ".foo{background:-o-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -o-linear-gradient(bottom, yellow, blue); }", + ".foo{background:-o-linear-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -o-linear-gradient(top right, red, white, blue) }", + ".foo{background:-o-linear-gradient(top right,red,#fff,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-gradient(linear, left top, left bottom, from(blue), to(yellow)) }", + ".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),to(#ff0))}", + ); + minifyTest( + ".foo { background: -webkit-gradient(linear, left top, left bottom, from(blue), color-stop(50%, red), to(yellow)) }", + ".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),color-stop(.5,red),to(#ff0))}", + ); + minifyTest( + ".foo { background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, blue), color-stop(50%, red), color-stop(100%, yellow)) }", + ".foo{background:-webkit-gradient(linear,0 0,0 100%,from(#00f),color-stop(.5,red),to(#ff0))}", + ); + minifyTest( + ".foo { background: repeating-linear-gradient(yellow 10px, blue 50px) }", + ".foo{background:repeating-linear-gradient(#ff0 10px,#00f 50px)}", + ); + minifyTest( + ".foo { background: -webkit-repeating-linear-gradient(yellow 10px, blue 50px) }", + ".foo{background:-webkit-repeating-linear-gradient(#ff0 10px,#00f 50px)}", + ); + minifyTest( + ".foo { background: -moz-repeating-linear-gradient(yellow 10px, blue 50px) }", + ".foo{background:-moz-repeating-linear-gradient(#ff0 10px,#00f 50px)}", + ); + minifyTest( + ".foo { background: -o-repeating-linear-gradient(yellow 10px, blue 50px) }", + ".foo{background:-o-repeating-linear-gradient(#ff0 10px,#00f 50px)}", + ); + minifyTest(".foo { background: radial-gradient(yellow, blue) }", ".foo{background:radial-gradient(#ff0,#00f)}"); + minifyTest( + ".foo { background: radial-gradient(at top left, yellow, blue) }", + ".foo{background:radial-gradient(at 0 0,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(5em circle at top left, yellow, blue) }", + ".foo{background:radial-gradient(5em at 0 0,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(circle at 100%, #333, #333 50%, #eee 75%, #333 75%) }", + ".foo{background:radial-gradient(circle at 100%,#333,#333 50%,#eee 75%,#333 75%)}", + ); + minifyTest( + ".foo { background: radial-gradient(farthest-corner circle at 100% 50%, #333, #333 50%, #eee 75%, #333 75%) }", + ".foo{background:radial-gradient(circle at 100%,#333,#333 50%,#eee 75%,#333 75%)}", + ); + minifyTest( + ".foo { background: radial-gradient(farthest-corner circle at 50% 50%, #333, #333 50%, #eee 75%, #333 75%) }", + ".foo{background:radial-gradient(circle,#333,#333 50%,#eee 75%,#333 75%)}", + ); + minifyTest( + ".foo { background: radial-gradient(ellipse at top, #e66465, transparent) }", + ".foo{background:radial-gradient(at top,#e66465,#0000)}", + ); + minifyTest( + ".foo { background: radial-gradient(20px, yellow, blue) }", + ".foo{background:radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(circle 20px, yellow, blue) }", + ".foo{background:radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(20px 40px, yellow, blue) }", + ".foo{background:radial-gradient(20px 40px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(ellipse 20px 40px, yellow, blue) }", + ".foo{background:radial-gradient(20px 40px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(ellipse calc(20px + 10px) 40px, yellow, blue) }", + ".foo{background:radial-gradient(30px 40px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(circle farthest-side, yellow, blue) }", + ".foo{background:radial-gradient(circle farthest-side,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(farthest-side circle, yellow, blue) }", + ".foo{background:radial-gradient(circle farthest-side,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(ellipse farthest-side, yellow, blue) }", + ".foo{background:radial-gradient(farthest-side,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: radial-gradient(farthest-side ellipse, yellow, blue) }", + ".foo{background:radial-gradient(farthest-side,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-radial-gradient(yellow, blue) }", + ".foo{background:-webkit-radial-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -moz-radial-gradient(yellow, blue) }", + ".foo{background:-moz-radial-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -o-radial-gradient(yellow, blue) }", + ".foo{background:-o-radial-gradient(#ff0,#00f)}", + ); + minifyTest( + ".foo { background: repeating-radial-gradient(circle 20px, yellow, blue) }", + ".foo{background:repeating-radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-repeating-radial-gradient(circle 20px, yellow, blue) }", + ".foo{background:-webkit-repeating-radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -moz-repeating-radial-gradient(circle 20px, yellow, blue) }", + ".foo{background:-moz-repeating-radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -o-repeating-radial-gradient(circle 20px, yellow, blue) }", + ".foo{background:-o-repeating-radial-gradient(20px,#ff0,#00f)}", + ); + minifyTest( + ".foo { background: -webkit-gradient(radial, center center, 0, center center, 100, from(blue), to(yellow)) }", + ".foo{background:-webkit-gradient(radial,50% 50%,0,50% 50%,100,from(#00f),to(#ff0))}", + ); + minifyTest(".foo { background: conic-gradient(#f06, gold) }", ".foo{background:conic-gradient(#f06,gold)}"); + minifyTest( + ".foo { background: conic-gradient(at 50% 50%, #f06, gold) }", + ".foo{background:conic-gradient(#f06,gold)}", + ); + minifyTest( + ".foo { background: conic-gradient(from 0deg, #f06, gold) }", + ".foo{background:conic-gradient(#f06,gold)}", + ); + minifyTest(".foo { background: conic-gradient(from 0, #f06, gold) }", ".foo{background:conic-gradient(#f06,gold)}"); + minifyTest( + ".foo { background: conic-gradient(from 0deg at center, #f06, gold) }", + ".foo{background:conic-gradient(#f06,gold)}", + ); + minifyTest( + ".foo { background: conic-gradient(white -50%, black 150%) }", + ".foo{background:conic-gradient(#fff -50%,#000 150%)}", + ); + minifyTest( + ".foo { background: conic-gradient(white -180deg, black 540deg) }", + ".foo{background:conic-gradient(#fff -180deg,#000 540deg)}", + ); + minifyTest( + ".foo { background: conic-gradient(from 45deg, white, black, white) }", + ".foo{background:conic-gradient(from 45deg,#fff,#000,#fff)}", + ); + minifyTest( + ".foo { background: repeating-conic-gradient(from 45deg, white, black, white) }", + ".foo{background:repeating-conic-gradient(from 45deg,#fff,#000,#fff)}", + ); + minifyTest( + ".foo { background: repeating-conic-gradient(black 0deg 25%, white 0deg 50%) }", + ".foo{background:repeating-conic-gradient(#000 0deg 25%,#fff 0deg 50%)}", + ); + }); });