mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
430 lines
18 KiB
Zig
430 lines
18 KiB
Zig
pub const css = @import("./css_parser.zig");
|
|
pub const css_values = @import("./values/values.zig");
|
|
pub const Error = css.Error;
|
|
const Location = css.Location;
|
|
|
|
/// A printer error.
|
|
pub const PrinterError = Err(PrinterErrorKind);
|
|
|
|
pub fn fmtPrinterError() PrinterError {
|
|
return .{
|
|
.kind = .fmt_error,
|
|
.loc = null,
|
|
};
|
|
}
|
|
|
|
/// An error with a source location.
|
|
pub fn Err(comptime T: type) type {
|
|
return struct {
|
|
/// The type of error that occurred.
|
|
kind: T,
|
|
/// The location where the error occurred.
|
|
loc: ?ErrorLocation,
|
|
|
|
pub fn fmt(
|
|
this: @This(),
|
|
comptime _: []const u8,
|
|
_: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
if (@hasDecl(T, "fmt")) {
|
|
try writer.print("{}", .{this.kind});
|
|
return;
|
|
}
|
|
@compileError("fmt not implemented for " ++ @typeName(T));
|
|
}
|
|
|
|
pub fn toJSString(this: @This(), allocator: Allocator, globalThis: *bun.jsc.JSGlobalObject) bun.jsc.JSValue {
|
|
var error_string = ArrayList(u8){};
|
|
defer error_string.deinit(allocator);
|
|
error_string.writer(allocator).print("{}", .{this.kind}) catch unreachable;
|
|
return bun.String.fromBytes(error_string.items).toJS(globalThis);
|
|
}
|
|
|
|
pub fn fromParseError(err: ParseError(ParserError), filename: []const u8) Err(ParserError) {
|
|
if (T != ParserError) {
|
|
@compileError("Called .fromParseError() when T is not ParserError");
|
|
}
|
|
|
|
const kind = switch (err.kind) {
|
|
.basic => |b| switch (b) {
|
|
.unexpected_token => |t| ParserError{ .unexpected_token = t },
|
|
.end_of_input => ParserError.end_of_input,
|
|
.at_rule_invalid => |a| ParserError{ .at_rule_invalid = a },
|
|
.at_rule_body_invalid => ParserError.at_rule_body_invalid,
|
|
.qualified_rule_invalid => ParserError.qualified_rule_invalid,
|
|
},
|
|
.custom => |c| c,
|
|
};
|
|
|
|
return .{
|
|
.kind = kind,
|
|
.loc = ErrorLocation{
|
|
.filename = filename,
|
|
.line = err.location.line,
|
|
.column = err.location.column,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn addToLogger(this: @This(), log: *logger.Log, source: *const logger.Source, allocator: std.mem.Allocator) !void {
|
|
try log.addMsg(.{
|
|
.kind = .err,
|
|
.data = .{
|
|
.location = if (this.loc) |*loc| try loc.toLocation(source, allocator) else null,
|
|
.text = try std.fmt.allocPrint(allocator, "{}", .{this.kind}),
|
|
},
|
|
});
|
|
|
|
log.errors += 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Extensible parse errors that can be encountered by client parsing implementations.
|
|
pub fn ParseError(comptime T: type) type {
|
|
return struct {
|
|
/// Details of this error
|
|
kind: ParserErrorKind(T),
|
|
/// Location where this error occurred
|
|
location: css.SourceLocation,
|
|
|
|
pub fn basic(this: @This()) BasicParseError {
|
|
return switch (this.kind) {
|
|
.basic => |kind| BasicParseError{
|
|
.kind = kind,
|
|
.location = this.location,
|
|
},
|
|
.custom => @panic("Not a basic parse error. This is a bug in Bun's css parser."),
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn ParserErrorKind(comptime T: type) type {
|
|
return union(enum) {
|
|
/// A fundamental parse error from a built-in parsing routine.
|
|
basic: BasicParseErrorKind,
|
|
/// A parse error reported by downstream consumer code.
|
|
custom: T,
|
|
|
|
pub fn format(this: @This(), comptime formatter: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
|
return switch (this) {
|
|
inline else => |kind| try kind.format(formatter, options, writer),
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Details about a `BasicParseError`
|
|
pub const BasicParseErrorKind = union(enum) {
|
|
/// An unexpected token was encountered.
|
|
unexpected_token: css.Token,
|
|
/// The end of the input was encountered unexpectedly.
|
|
end_of_input,
|
|
/// An `@` rule was encountered that was invalid.
|
|
at_rule_invalid: []const u8,
|
|
/// The body of an '@' rule was invalid.
|
|
at_rule_body_invalid,
|
|
/// A qualified rule was encountered that was invalid.
|
|
qualified_rule_invalid,
|
|
|
|
pub fn format(this: BasicParseErrorKind, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
|
|
_ = fmt; // autofix
|
|
_ = opts; // autofix
|
|
return switch (this) {
|
|
.unexpected_token => |token| {
|
|
try writer.print("unexpected token: {}", .{token});
|
|
},
|
|
.end_of_input => {
|
|
try writer.print("unexpected end of input", .{});
|
|
},
|
|
.at_rule_invalid => |rule| {
|
|
try writer.print("invalid @ rule encountered: '@{s}'", .{rule});
|
|
},
|
|
.at_rule_body_invalid => {
|
|
// try writer.print("invalid @ body rule encountered: '@{s}'", .{});
|
|
try writer.print("invalid @ body rule encountered", .{});
|
|
},
|
|
.qualified_rule_invalid => {
|
|
try writer.print("invalid qualified rule encountered", .{});
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
/// A line and column location within a source file.
|
|
pub const ErrorLocation = struct {
|
|
/// The filename in which the error occurred.
|
|
filename: []const u8,
|
|
/// The line number, starting from 0.
|
|
line: u32,
|
|
/// The column number, starting from 1.
|
|
column: u32,
|
|
|
|
pub fn withFilename(this: ErrorLocation, filename: []const u8) ErrorLocation {
|
|
return ErrorLocation{
|
|
.filename = filename,
|
|
.line = this.line,
|
|
.column = this.column,
|
|
};
|
|
}
|
|
|
|
pub fn format(this: *const @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
try writer.print("{s}:{d}:{d}", .{ this.filename, this.line, this.column });
|
|
}
|
|
|
|
pub fn toLocation(this: @This(), source: *const logger.Source, allocator: Allocator) !logger.Location {
|
|
return logger.Location{
|
|
.file = source.path.text,
|
|
.namespace = source.path.namespace,
|
|
.line = @intCast(this.line + 1),
|
|
.column = @intCast(this.column),
|
|
.line_text = if (bun.strings.getLinesInText(source.contents, this.line, 1)) |lines| try allocator.dupe(u8, lines.buffer[0]) else null,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// A printer error type.
|
|
pub const PrinterErrorKind = union(enum) {
|
|
/// An ambiguous relative `url()` was encountered in a custom property declaration.
|
|
ambiguous_url_in_custom_property: struct {
|
|
/// The ambiguous URL.
|
|
url: []const u8,
|
|
},
|
|
/// A [std::fmt::Error](std::fmt::Error) was encountered in the underlying destination.
|
|
fmt_error,
|
|
/// The CSS modules `composes` property cannot be used within nested rules.
|
|
invalid_composes_nesting,
|
|
/// The CSS modules `composes` property cannot be used with a simple class selector.
|
|
invalid_composes_selector,
|
|
/// The CSS modules pattern must end with `[local]` for use in CSS grid.
|
|
invalid_css_modules_pattern_in_grid,
|
|
no_import_records,
|
|
|
|
pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
return switch (this) {
|
|
.ambiguous_url_in_custom_property => |data| writer.print("Ambiguous relative URL '{s}' in custom property declaration", .{data.url}),
|
|
.fmt_error => writer.writeAll("Formatting error occurred"),
|
|
.invalid_composes_nesting => writer.writeAll("The 'composes' property cannot be used within nested rules"),
|
|
.invalid_composes_selector => writer.writeAll("The 'composes' property can only be used with a simple class selector"),
|
|
.invalid_css_modules_pattern_in_grid => writer.writeAll("CSS modules pattern must end with '[local]' when used in CSS grid"),
|
|
.no_import_records => writer.writeAll("No import records found"),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// A parser error.
|
|
pub const ParserError = union(enum) {
|
|
/// An at rule body was invalid.
|
|
at_rule_body_invalid,
|
|
/// An at rule prelude was invalid.
|
|
at_rule_prelude_invalid,
|
|
/// An unknown or unsupported at rule was encountered.
|
|
at_rule_invalid: []const u8,
|
|
/// Unexpectedly encountered the end of input data.
|
|
end_of_input,
|
|
/// A declaration was invalid.
|
|
invalid_declaration,
|
|
/// A media query was invalid.
|
|
invalid_media_query,
|
|
/// Invalid CSS nesting.
|
|
invalid_nesting,
|
|
/// The @nest rule is deprecated.
|
|
deprecated_nest_rule,
|
|
/// An invalid selector in an `@page` rule.
|
|
invalid_page_selector,
|
|
/// An invalid value was encountered.
|
|
invalid_value,
|
|
/// Invalid qualified rule.
|
|
qualified_rule_invalid,
|
|
/// A selector was invalid.
|
|
selector_error: SelectorError,
|
|
/// An `@import` rule was encountered after any rule besides `@charset` or `@layer`.
|
|
unexpected_import_rule,
|
|
/// A `@namespace` rule was encountered after any rules besides `@charset`, `@import`, or `@layer`.
|
|
unexpected_namespace_rule,
|
|
/// An unexpected token was encountered.
|
|
unexpected_token: css.Token,
|
|
/// Maximum nesting depth was reached.
|
|
maximum_nesting_depth,
|
|
unexpected_value: struct {
|
|
expected: []const u8,
|
|
received: []const u8,
|
|
},
|
|
|
|
pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
return switch (this) {
|
|
.at_rule_body_invalid => writer.writeAll("Invalid at-rule body"),
|
|
.at_rule_prelude_invalid => writer.writeAll("Invalid at-rule prelude"),
|
|
.at_rule_invalid => |name| writer.print("Unknown at-rule @{s}", .{name}),
|
|
.end_of_input => writer.writeAll("Unexpected end of input"),
|
|
.invalid_declaration => writer.writeAll("Invalid declaration"),
|
|
.invalid_media_query => writer.writeAll("Invalid media query"),
|
|
.invalid_nesting => writer.writeAll("Invalid CSS nesting"),
|
|
.deprecated_nest_rule => writer.writeAll("The @nest rule is deprecated, use standard CSS nesting instead"),
|
|
.invalid_page_selector => writer.writeAll("Invalid @page selector"),
|
|
.invalid_value => writer.writeAll("Invalid value"),
|
|
.qualified_rule_invalid => writer.writeAll("Invalid qualified rule"),
|
|
.selector_error => |err| writer.print("Invalid selector. {s}", .{err}),
|
|
.unexpected_import_rule => writer.writeAll("@import rules must come before any other rules except @charset and @layer"),
|
|
.unexpected_namespace_rule => writer.writeAll("@namespace rules must come before any other rules except @charset, @import, and @layer"),
|
|
.unexpected_token => |token| writer.print("Unexpected token: {}", .{token}),
|
|
.maximum_nesting_depth => writer.writeAll("Maximum CSS nesting depth exceeded"),
|
|
.unexpected_value => |v| writer.print("Expected {s}, received {s}", .{ v.expected, v.received }),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// The fundamental parsing errors that can be triggered by built-in parsing routines.
|
|
pub const BasicParseError = struct {
|
|
/// Details of this error
|
|
kind: BasicParseErrorKind,
|
|
/// Location where this error occurred
|
|
location: css.SourceLocation,
|
|
|
|
pub fn intoParseError(
|
|
this: @This(),
|
|
comptime T: type,
|
|
) ParseError(T) {
|
|
return ParseError(T){
|
|
.kind = .{ .basic = this.kind },
|
|
.location = this.location,
|
|
};
|
|
}
|
|
|
|
pub inline fn intoDefaultParseError(
|
|
this: @This(),
|
|
) ParseError(ParserError) {
|
|
return ParseError(ParserError){
|
|
.kind = .{ .basic = this.kind },
|
|
.location = this.location,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// A selector parsing error.
|
|
pub const SelectorError = union(enum) {
|
|
/// An unexpected token was found in an attribute selector.
|
|
bad_value_in_attr: css.Token,
|
|
/// An unexpected token was found in a class selector.
|
|
class_needs_ident: css.Token,
|
|
/// A dangling combinator was found.
|
|
dangling_combinator,
|
|
/// An empty selector.
|
|
empty_selector,
|
|
/// A `|` was expected in an attribute selector.
|
|
expected_bar_in_attr: css.Token,
|
|
/// A namespace was expected.
|
|
expected_namespace: []const u8,
|
|
/// An unexpected token was encountered in a namespace.
|
|
explicit_namespace_unexpected_token: css.Token,
|
|
/// An invalid pseudo class was encountered after a pseudo element.
|
|
invalid_pseudo_class_after_pseudo_element,
|
|
/// An invalid pseudo class was encountered after a `-webkit-scrollbar` pseudo element.
|
|
invalid_pseudo_class_after_webkit_scrollbar,
|
|
/// A `-webkit-scrollbar` state was encountered before a `-webkit-scrollbar` pseudo element.
|
|
invalid_pseudo_class_before_webkit_scrollbar,
|
|
/// Invalid qualified name in attribute selector.
|
|
invalid_qual_name_in_attr: css.Token,
|
|
/// The current token is not allowed in this state.
|
|
invalid_state,
|
|
/// The selector is required to have the `&` nesting selector at the start.
|
|
missing_nesting_prefix,
|
|
/// The selector is missing a `&` nesting selector.
|
|
missing_nesting_selector,
|
|
/// No qualified name in attribute selector.
|
|
no_qualified_name_in_attribute_selector: css.Token,
|
|
/// An invalid token was encountered in a pseudo element.
|
|
pseudo_element_expected_ident: css.Token,
|
|
/// An unexpected identifier was encountered.
|
|
unexpected_ident: []const u8,
|
|
/// An unexpected token was encountered inside an attribute selector.
|
|
unexpected_token_in_attribute_selector: css.Token,
|
|
/// An unsupported pseudo class or pseudo element was encountered.
|
|
unsupported_pseudo_class_or_element: []const u8,
|
|
unexpected_selector_after_pseudo_element: css.Token,
|
|
ambiguous_css_module_class: []const u8,
|
|
|
|
pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
return switch (this) {
|
|
.dangling_combinator => try writer.writeAll("Found a dangling combinator with no selector"),
|
|
.empty_selector => try writer.writeAll("Empty selector is not allowed"),
|
|
.invalid_state => try writer.writeAll("Token is not allowed in this state"),
|
|
.missing_nesting_prefix => try writer.writeAll("Selector must start with the '&' nesting selector"),
|
|
.missing_nesting_selector => try writer.writeAll("Missing '&' nesting selector"),
|
|
.invalid_pseudo_class_after_pseudo_element => try writer.writeAll("Invalid pseudo-class after pseudo-element"),
|
|
.invalid_pseudo_class_after_webkit_scrollbar => try writer.writeAll("Invalid pseudo-class after -webkit-scrollbar"),
|
|
.invalid_pseudo_class_before_webkit_scrollbar => try writer.writeAll("-webkit-scrollbar state found before -webkit-scrollbar pseudo-element"),
|
|
|
|
.expected_namespace => |str| try writer.print("Expected namespace '{s}'", .{str}),
|
|
.unexpected_ident => |str| try writer.print("Unexpected identifier '{s}'", .{str}),
|
|
.unsupported_pseudo_class_or_element => |str| try writer.print("Unsupported pseudo-class or pseudo-element '{s}'", .{str}),
|
|
|
|
.bad_value_in_attr => |tok| try writer.print("Invalid value in attribute selector: {}", .{tok}),
|
|
.class_needs_ident => |tok| try writer.print("Expected identifier after '.' in class selector, found: {}", .{tok}),
|
|
.expected_bar_in_attr => |tok| try writer.print("Expected '|' in attribute selector, found: {}", .{tok}),
|
|
.explicit_namespace_unexpected_token => |tok| try writer.print("Unexpected token in namespace: {}", .{tok}),
|
|
.invalid_qual_name_in_attr => |tok| try writer.print("Invalid qualified name in attribute selector: {}", .{tok}),
|
|
.no_qualified_name_in_attribute_selector => |tok| try writer.print("Missing qualified name in attribute selector: {}", .{tok}),
|
|
.pseudo_element_expected_ident => |tok| try writer.print("Expected identifier in pseudo-element, found: {}", .{tok}),
|
|
.unexpected_token_in_attribute_selector => |tok| try writer.print("Unexpected token in attribute selector: {}", .{tok}),
|
|
.unexpected_selector_after_pseudo_element => |tok| try writer.print("Unexpected selector after pseudo-element: {}", .{tok}),
|
|
.ambiguous_css_module_class => |name| try writer.print("CSS module class: '{s}' is currently not supported.", .{name}),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn ErrorWithLocation(comptime T: type) type {
|
|
return struct {
|
|
kind: T,
|
|
loc: css.Location,
|
|
};
|
|
}
|
|
|
|
pub const MinifyErr = error{
|
|
minify_err,
|
|
};
|
|
pub const MinifyError = ErrorWithLocation(MinifyErrorKind);
|
|
/// A transformation error.
|
|
pub const MinifyErrorKind = union(enum) {
|
|
/// A circular `@custom-media` rule was detected.
|
|
circular_custom_media: struct {
|
|
/// The name of the `@custom-media` rule that was referenced circularly.
|
|
name: []const u8,
|
|
},
|
|
/// Attempted to reference a custom media rule that doesn't exist.
|
|
custom_media_not_defined: struct {
|
|
/// The name of the `@custom-media` rule that was not defined.
|
|
name: []const u8,
|
|
},
|
|
/// Boolean logic with media types in @custom-media rules is not supported.
|
|
unsupported_custom_media_boolean_logic: struct {
|
|
/// The source location of the `@custom-media` rule with unsupported boolean logic.
|
|
custom_media_loc: Location,
|
|
},
|
|
|
|
pub fn format(this: *const @This(), comptime _: []const u8, _: anytype, writer: anytype) !void {
|
|
return switch (this.*) {
|
|
.circular_custom_media => |name| try writer.print("Circular @custom-media rule: \"{s}\"", .{name.name}),
|
|
.custom_media_not_defined => |name| try writer.print("Custom media rule \"{s}\" not defined", .{name.name}),
|
|
.unsupported_custom_media_boolean_logic => |custom_media_loc| try writer.print(
|
|
"Unsupported boolean logic in custom media rule at line {d}, column {d}",
|
|
.{
|
|
custom_media_loc.custom_media_loc.line,
|
|
custom_media_loc.custom_media_loc.column,
|
|
},
|
|
),
|
|
};
|
|
}
|
|
};
|
|
|
|
const bun = @import("bun");
|
|
|
|
const logger = bun.logger;
|
|
const Log = logger.Log;
|
|
|
|
const std = @import("std");
|
|
const ArrayList = std.ArrayListUnmanaged;
|
|
const Allocator = std.mem.Allocator;
|