use bstr::BStr;
use core::fmt;
use crate::{Location, SourceLocation, Token};
use crate::Str;
#[inline(always)]
fn bs(p: Str) -> &'static BStr {
BStr::new(unsafe { crate::arena_str(p) })
}
pub type PrinterError = Err<PrinterErrorKind>;
pub fn fmt_printer_error() -> PrinterError {
Err {
kind: PrinterErrorKind::fmt_error,
loc: None,
}
}
pub struct Err<T> {
pub kind: T,
pub loc: Option<ErrorLocation>,
}
impl<T: fmt::Display> fmt::Display for Err<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl Err<ParserError> {
pub fn from_parse_error(err: ParseError<ParserError>, filename: &[u8]) -> Err<ParserError> {
let kind = match err.kind {
ParserErrorKind::basic(b) => match b {
BasicParseErrorKind::unexpected_token(t) => ParserError::unexpected_token(t),
BasicParseErrorKind::end_of_input => ParserError::end_of_input,
BasicParseErrorKind::at_rule_invalid(a) => ParserError::at_rule_invalid(a),
BasicParseErrorKind::at_rule_body_invalid => ParserError::at_rule_body_invalid,
BasicParseErrorKind::qualified_rule_invalid => ParserError::qualified_rule_invalid,
},
ParserErrorKind::custom(c) => c,
};
Err {
kind,
loc: Some(ErrorLocation {
filename,
line: err.location.line,
column: err.location.column,
}),
}
}
}
impl<T: fmt::Display> Err<T> {
pub fn add_to_logger(
&self,
log: &mut bun_ast::Log,
source: &bun_ast::Source,
) -> Result<(), bun_core::Error> {
use bun_core::OrWriteFailed as _;
use std::io::Write as _;
let mut text: Vec<u8> = Vec::new();
write!(&mut text, "{}", self.kind).or_write_failed()?;
log.add_msg(bun_ast::Msg {
kind: bun_ast::Kind::Err,
data: bun_ast::Data {
location: match &self.loc {
Some(loc) => Some(loc.to_location(source)?),
None => None,
},
text: text.into(),
},
..Default::default()
});
log.errors += 1;
Ok(())
}
}
pub struct ParseError<T> {
pub kind: ParserErrorKind<T>,
pub location: SourceLocation,
}
impl<T> ParseError<T> {
pub fn basic(self) -> BasicParseError {
match self.kind {
ParserErrorKind::basic(kind) => BasicParseError {
kind,
location: self.location,
},
ParserErrorKind::custom(_) => {
panic!("Not a basic parse error. This is a bug in Bun's css parser.")
}
}
}
}
#[allow(non_camel_case_types)]
pub enum ParserErrorKind<T> {
basic(BasicParseErrorKind),
custom(T),
}
impl<T: fmt::Display> fmt::Display for ParserErrorKind<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::basic(kind) => kind.fmt(f),
Self::custom(kind) => kind.fmt(f),
}
}
}
#[allow(non_camel_case_types)]
pub enum BasicParseErrorKind {
unexpected_token(Token),
end_of_input,
at_rule_invalid(Str),
at_rule_body_invalid,
qualified_rule_invalid,
}
impl fmt::Display for BasicParseErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::unexpected_token(token) => {
write!(f, "unexpected token: {}", token)
}
Self::end_of_input => {
write!(f, "unexpected end of input")
}
Self::at_rule_invalid(rule) => {
write!(f, "invalid @ rule encountered: '@{}'", bs(*rule))
}
Self::at_rule_body_invalid => {
write!(f, "invalid @ body rule encountered")
}
Self::qualified_rule_invalid => {
write!(f, "invalid qualified rule encountered")
}
}
}
}
pub struct ErrorLocation {
pub filename: Str,
pub line: u32,
pub column: u32,
}
impl ErrorLocation {
pub fn with_filename(&self, filename: &[u8]) -> ErrorLocation {
ErrorLocation {
filename,
line: self.line,
column: self.column,
}
}
pub fn to_location(
&self,
source: &bun_ast::Source,
) -> Result<bun_ast::Location, bun_core::Error> {
let line_text = bun_core::strings::get_lines_in_text::<1>(&source.contents, self.line)
.map(|lines| unsafe { &*std::ptr::from_ref::<[u8]>(lines.as_slice()[0]) });
Ok(bun_ast::Location {
file: std::borrow::Cow::Borrowed(source.path.text),
namespace: source.path.namespace,
line: i32::try_from(self.line + 1).expect("int cast"),
column: i32::try_from(self.column).expect("int cast"),
line_text: line_text.map(std::borrow::Cow::Borrowed),
..Default::default()
})
}
}
impl fmt::Display for ErrorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", bs(self.filename), self.line, self.column)
}
}
#[allow(non_camel_case_types)]
pub enum PrinterErrorKind {
ambiguous_url_in_custom_property {
url: Str,
},
fmt_error,
invalid_composes_nesting,
invalid_composes_selector,
invalid_css_modules_pattern_in_grid,
maximum_nesting_expansion,
no_import_records,
}
impl fmt::Display for PrinterErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ambiguous_url_in_custom_property { url } => write!(
f,
"Ambiguous relative URL '{}' in custom property declaration",
bs(*url)
),
Self::fmt_error => f.write_str("Formatting error occurred"),
Self::invalid_composes_nesting => {
f.write_str("The 'composes' property cannot be used within nested rules")
}
Self::invalid_composes_selector => {
f.write_str("The 'composes' property can only be used with a simple class selector")
}
Self::invalid_css_modules_pattern_in_grid => {
f.write_str("CSS modules pattern must end with '[local]' when used in CSS grid")
}
Self::maximum_nesting_expansion => f.write_str(
"Maximum nesting expansion exceeded when compiling CSS nesting for the configured targets",
),
Self::no_import_records => f.write_str("No import records found"),
}
}
}
#[allow(non_camel_case_types)]
pub enum ParserError {
at_rule_body_invalid,
at_rule_prelude_invalid,
at_rule_invalid(Str),
end_of_input,
invalid_declaration,
invalid_media_query,
invalid_nesting,
deprecated_nest_rule,
invalid_page_selector,
invalid_value,
qualified_rule_invalid,
selector_error(SelectorError),
unexpected_import_rule,
unexpected_namespace_rule,
unexpected_token(Token),
maximum_nesting_depth,
unexpected_value {
expected: Str,
received: Str,
},
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::at_rule_body_invalid => f.write_str("Invalid at-rule body"),
Self::at_rule_prelude_invalid => f.write_str("Invalid at-rule prelude"),
Self::at_rule_invalid(name) => write!(f, "Unknown at-rule @{}", bs(*name)),
Self::end_of_input => f.write_str("Unexpected end of input"),
Self::invalid_declaration => f.write_str("Invalid declaration"),
Self::invalid_media_query => f.write_str("Invalid media query"),
Self::invalid_nesting => f.write_str("Invalid CSS nesting"),
Self::deprecated_nest_rule => {
f.write_str("The @nest rule is deprecated, use standard CSS nesting instead")
}
Self::invalid_page_selector => f.write_str("Invalid @page selector"),
Self::invalid_value => f.write_str("Invalid value"),
Self::qualified_rule_invalid => f.write_str("Invalid qualified rule"),
Self::selector_error(err) => write!(f, "Invalid selector. {}", err),
Self::unexpected_import_rule => f.write_str(
"@import rules must come before any other rules except @charset and @layer",
),
Self::unexpected_namespace_rule => f.write_str(
"@namespace rules must come before any other rules except @charset, @import, and @layer",
),
Self::unexpected_token(token) => write!(f, "Unexpected token: {}", token),
Self::maximum_nesting_depth => f.write_str("Maximum CSS nesting depth exceeded"),
Self::unexpected_value { expected, received } => {
write!(f, "Expected {}, received {}", bs(*expected), bs(*received))
}
}
}
}
pub struct BasicParseError {
pub kind: BasicParseErrorKind,
pub location: SourceLocation,
}
impl BasicParseError {
pub fn into_parse_error<T>(self) -> ParseError<T> {
ParseError {
kind: ParserErrorKind::basic(self.kind),
location: self.location,
}
}
#[inline]
pub fn into_default_parse_error(self) -> ParseError<ParserError> {
ParseError {
kind: ParserErrorKind::basic(self.kind),
location: self.location,
}
}
}
#[allow(non_camel_case_types)]
pub enum SelectorError {
bad_value_in_attr(Token),
class_needs_ident(Token),
dangling_combinator,
empty_selector,
expected_bar_in_attr(Token),
expected_namespace(Str),
explicit_namespace_unexpected_token(Token),
invalid_pseudo_class_after_pseudo_element,
invalid_pseudo_class_after_webkit_scrollbar,
invalid_pseudo_class_before_webkit_scrollbar,
invalid_qual_name_in_attr(Token),
invalid_state,
missing_nesting_prefix,
missing_nesting_selector,
no_qualified_name_in_attribute_selector(Token),
pseudo_element_expected_ident(Token),
unexpected_ident(Str),
unexpected_token_in_attribute_selector(Token),
unsupported_pseudo_class_or_element(Str),
unexpected_selector_after_pseudo_element(Token),
ambiguous_css_module_class(Str),
}
impl fmt::Display for SelectorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::dangling_combinator => {
f.write_str("Found a dangling combinator with no selector")
}
Self::empty_selector => f.write_str("Empty selector is not allowed"),
Self::invalid_state => f.write_str("Token is not allowed in this state"),
Self::missing_nesting_prefix => {
f.write_str("Selector must start with the '&' nesting selector")
}
Self::missing_nesting_selector => f.write_str("Missing '&' nesting selector"),
Self::invalid_pseudo_class_after_pseudo_element => {
f.write_str("Invalid pseudo-class after pseudo-element")
}
Self::invalid_pseudo_class_after_webkit_scrollbar => {
f.write_str("Invalid pseudo-class after -webkit-scrollbar")
}
Self::invalid_pseudo_class_before_webkit_scrollbar => {
f.write_str("-webkit-scrollbar state found before -webkit-scrollbar pseudo-element")
}
Self::expected_namespace(s) => write!(f, "Expected namespace '{}'", bs(*s)),
Self::unexpected_ident(s) => write!(f, "Unexpected identifier '{}'", bs(*s)),
Self::unsupported_pseudo_class_or_element(s) => {
write!(f, "Unsupported pseudo-class or pseudo-element '{}'", bs(*s))
}
Self::bad_value_in_attr(tok) => {
write!(f, "Invalid value in attribute selector: {}", tok)
}
Self::class_needs_ident(tok) => write!(
f,
"Expected identifier after '.' in class selector, found: {}",
tok
),
Self::expected_bar_in_attr(tok) => {
write!(f, "Expected '|' in attribute selector, found: {}", tok)
}
Self::explicit_namespace_unexpected_token(tok) => {
write!(f, "Unexpected token in namespace: {}", tok)
}
Self::invalid_qual_name_in_attr(tok) => {
write!(f, "Invalid qualified name in attribute selector: {}", tok)
}
Self::no_qualified_name_in_attribute_selector(tok) => {
write!(f, "Missing qualified name in attribute selector: {}", tok)
}
Self::pseudo_element_expected_ident(tok) => {
write!(f, "Expected identifier in pseudo-element, found: {}", tok)
}
Self::unexpected_token_in_attribute_selector(tok) => {
write!(f, "Unexpected token in attribute selector: {}", tok)
}
Self::unexpected_selector_after_pseudo_element(tok) => {
write!(f, "Unexpected selector after pseudo-element: {}", tok)
}
Self::ambiguous_css_module_class(name) => write!(
f,
"CSS module class: '{}' is currently not supported.",
bs(*name)
),
}
}
}
pub struct ErrorWithLocation<T> {
pub kind: T,
pub loc: Location,
}
#[derive(strum::IntoStaticStr, Debug)]
#[allow(non_camel_case_types)]
pub enum MinifyErr {
minify_err,
}
bun_core::impl_tag_error!(MinifyErr);
bun_core::named_error_set!(MinifyErr);
pub type MinifyError = ErrorWithLocation<MinifyErrorKind>;
#[allow(non_camel_case_types)]
pub enum MinifyErrorKind {
circular_custom_media {
name: Str,
},
custom_media_not_defined {
name: Str,
},
unsupported_custom_media_boolean_logic {
custom_media_loc: Location,
},
selector_expansion_limit_exceeded,
unknown,
}
impl fmt::Display for MinifyErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::circular_custom_media { name } => {
write!(f, "Circular @custom-media rule: \"{}\"", bs(*name))
}
Self::custom_media_not_defined { name } => {
write!(f, "Custom media rule \"{}\" not defined", bs(*name))
}
Self::unsupported_custom_media_boolean_logic { custom_media_loc } => write!(
f,
"Unsupported boolean logic in custom media rule at line {}, column {}",
custom_media_loc.line, custom_media_loc.column,
),
Self::selector_expansion_limit_exceeded => write!(
f,
"Nested CSS rules expand to more than {} selectors when compiled for the configured browser targets. Reduce the nesting depth or the number of selectors per rule, or target browsers that support CSS nesting.",
crate::css_rules::MAX_SELECTOR_EXPANSION,
),
Self::unknown => write!(f, "CSS minification failed"),
}
}
}