use super::{
attribute,
fluent::{self, GroupError},
MacroError, ParseError, UnsupportedError, UnsupportedReason,
};
use proc_macro2::TokenStream;
use proc_macro_error2::{emit_call_site_error, emit_error};
use syn::spanned::Spanned;
fn attribute(error: attribute::Error, attribute_stream: TokenStream) {
match error {
attribute::Error::Build(build_error) => {
for located_error in build_error.locations() {
let error_source = attribute_stream.span();
emit_error! { error_source, "invalid glob";
note = located_error.to_string();
};
}
}
attribute::Error::Walk(walk_error) => {
let help = if let Some(path) = walk_error.path() {
let path_name = path.to_str().unwrap();
match path.try_exists() {
Ok(true) => {
format!("the path `{path_name}` exists, but unable to access it")
}
_ => format!("the path `{path_name}` doesn't seem to exist"),
}
} else {
String::from("no associated path")
};
emit_error! { attribute_stream, "error at depth {} while walking path", walk_error.depth();
help = help;
};
}
attribute::Error::NoMatchesInEntry {
path,
complete_match,
} => {
assert!(path.exists());
assert_eq!(path.to_string_lossy(), complete_match);
emit_error! { attribute_stream, "cannot match against an exact path";
help = "The attribute should use glob syntax to match against multiple files";
note = "For example, you can:\n{}\n{}",
"- Match against directories: locale/**/errors.ftl",
"- Match against files: locale/*.ftl";
};
}
attribute::Error::NoMatchesInGlob => {
emit_error! { attribute_stream, "no matches found for glob";
help = "The attribute should use glob syntax to match against multiple files";
note = "For example, you can:\n{}\n{}",
"- Match against directories: locale/**/errors.ftl",
"- Match against files: locale/*.ftl";
};
}
attribute::Error::InvalidLocale {
path,
locale,
source,
} => {
emit_error! { attribute_stream, "invalid locale in file path";
help = "Expected `{}` to be a valid locale identifier while reading `{}`", locale, path.display();
note = source;
}
}
}
}
fn fluent(error: fluent::Error) {
eprintln!("{:?}", miette::Error::new(error));
emit_call_site_error!("invalid Fluent source code, see above for details");
}
fn group(error: GroupError, attribute_stream: TokenStream) {
match error {
GroupError::Fluent(error) => fluent(error),
GroupError::InvalidMessage {
fluent_name,
rust_name,
span,
} => {
emit_error! { span.unwrap(), "no matching Fluent message in canonical locale";
help = "The field `{}` implies the Fluent message `{}`", rust_name, fluent_name
};
}
GroupError::MissingCanonicalLocale {
canonical_locale,
matched_locales,
} => emit_error! { attribute_stream, "missing canonical locale `{}`", canonical_locale;
help = "{}", matched_locales;
},
}
}
fn unsupported(error: UnsupportedError) {
match error.reason {
UnsupportedReason::Union => emit_error! { error.span, "unions are not supported";
help = "Use a `struct` or `enum` instead"
},
UnsupportedReason::UnnamedFields => {
emit_error! { error.span, "only named fields are supported";
help = "Each field needs a name so it can be referenced by Fluent code";
note = "There must be at least one named field (unit structs are unsupported!)";
}
}
}
}
fn parse(error: ParseError, attribute_stream: TokenStream, derive_input_stream: TokenStream) {
match error {
ParseError::InvalidAttribute(invalid_attribute) => {
emit_error! { attribute_stream, invalid_attribute;
help = "Expected a path glob, for example {}",
r#"#[localize("i18n/**/strings.ftl")]"#;
}
}
ParseError::InvalidDeriveInput(invalid_derive_input) => {
emit_error! { derive_input_stream, invalid_derive_input;
help = "This macro can only be used on structs or enums";
}
}
}
}
pub fn emit(error: MacroError, attribute_stream: TokenStream, derive_input_stream: TokenStream) {
match error {
MacroError::Attribute(error) => attribute(error, attribute_stream),
MacroError::Group(error) => group(error, attribute_stream),
MacroError::Unsupported(error) => unsupported(error),
MacroError::ParseError(error) => parse(error, attribute_stream, derive_input_stream),
}
}