Emit compilation errors from Fluent source code

finchie
Jul 8, 2024, 6:53 AM
F5LG7WENUUDRSCTDMA4M6BAC5RWTGQO45C4ZEBZDX6FHCTTHBVGQC

Dependencies

  • [2] JZXXFWQK Add tests for `locale_select` backends on unix
  • [3] RLX6XPNZ Return an error when user provides an exact path
  • [4] BQ6N55O7 Refactor how `Group` stores messages
  • [5] 4MRF5E76 Generate simple locale matching code in `localize()`
  • [6] HJMYJDC7 Simplify `fluent_embed::group` module
  • [7] 5TEX4MNU Split `fluent_embed` into `group` and `parse` modules
  • [8] 2XQ6ZB4W Store multiple locales in a single `Group`
  • [9] SHNZZSZG Create `cli_macros` shim crate
  • [10] XEEXWJLG Add simple end-to-end test for selectors
  • [11] D652S2N3 Rename `parse` module to `parse_fluent`
  • [12] XGNME3WR Move `Group::derive_enum` to new `crate::parse_macro` module
  • [13] VNSHGQYN Support using glob paths in `localize` macro
  • [14] UOMQT7LT Add support for cardinal CLDR plural selectors
  • [15] ROSR4HD5 Parse captured glob as locale
  • [16] 5FIVUZYF Unify `fluent_embed` macro API as `localize()`
  • [17] V5S5K33A Add basic error handling for invalid paths in proc_macro attribute
  • [18] HCGVXOF7 Add language negotiation using `fluent-langneg`
  • [19] QSK7JRBA Add simple `attribute_path` function
  • [20] P6FW2GGO Remove unnecessary parameters in generated `localize()` function
  • [21] 3WEPY3OX Add `locale` parameter to derived `localize()` function
  • [22] VYPJUPPK Handle `GlobError::Build` in proc_macro error reporting
  • [23] O77KA6C4 Create `fluent_embed` crate
  • [24] OCR4YRQ2 Parse group from fluent file specified by macro attribute
  • [25] NO3PDO7P Refactor `fluent_embed` to support structs
  • [*] UKFEFT6L Create basic `Output` proc-macro
  • [*] WBI5HFOB Add simple wrapper for `libc::settext()` to query system locale
  • [*] 56F2YE6H Use `prettyplease` to format macro output
  • [*] VZYZRAO4 Move `output-macros` crate into workspace

Change contents

  • replacement in fluent_embed/src/parse_macro.rs at line 1
    [4.39][4.0:56]()
    use crate::{group::Group, parse_fluent::ReferenceKind};
    [4.39]
    [4.31]
    use crate::group::Group;
    use crate::parse_fluent::{FluentError, ReferenceKind, References};
    use std::collections::HashSet;
  • replacement in fluent_embed/src/parse_macro.rs at line 10
    [4.138][4.100:193]()
    fn expr_for_message(group: &Group, id: &str, reference_kind: ReferenceKind) -> TokenStream {
    [4.138]
    [4.193]
    fn expr_for_message(
    group: &Group,
    id: &str,
    reference_kind: &References,
    ) -> Result<TokenStream, FluentError> {
  • replacement in fluent_embed/src/parse_macro.rs at line 16
    [4.261][4.261:334]()
    let canonical_message = group.canonical_message(id, reference_kind);
    [4.261]
    [4.397]
    let canonical_message = group.canonical_message(id, reference_kind)?;
  • replacement in fluent_embed/src/parse_macro.rs at line 18
    [4.398][4.335:468]()
    let (additional_locales, additional_messages): (Vec<_>, Vec<_>) =
    group.additional_messages(id, reference_kind).unzip();
    [4.398]
    [4.468]
    let (additional_locales, additional_messages): (Vec<_>, Vec<_>) = group
    .additional_messages(id, reference_kind)?
    .into_iter()
    .unzip();
  • replacement in fluent_embed/src/parse_macro.rs at line 28
    [4.270][4.1355:1368](),[4.335][4.1355:1368](),[4.1355][4.1355:1368]()
    quote! {
    [4.664]
    [4.608]
    Ok(quote! {
  • replacement in fluent_embed/src/parse_macro.rs at line 40
    [4.1302][4.36:42](),[4.36][4.36:42]()
    }
    [4.1302]
    [4.42]
    })
    }
    /// Create a list of unique field names that can be referenced
    fn unique_named_fields(named_fields: &syn::FieldsNamed) -> HashSet<String> {
    named_fields
    .named
    .iter()
    // Get the `syn::Ident` for each field
    .map(|field| {
    field
    .ident
    .as_ref()
    .expect("Named fields should have an associated ident")
    })
    .map(|ident| ident.to_string())
    .collect::<HashSet<String>>()
  • replacement in fluent_embed/src/parse_macro.rs at line 59
    [4.45][4.1303:1451]()
    // TODO: check that the fields in fluent source reference fields that exist
    pub fn derive_struct(group: Group, ident: &syn::Ident) -> TokenStream {
    [4.45]
    [4.1451]
    pub fn derive_struct(
    group: Group,
    ident: &syn::Ident,
    fields: &syn::Fields,
    ) -> Result<TokenStream, FluentError> {
    // Turn the struct fields into a list of valid references
    let references = match fields {
    syn::Fields::Named(named_fields) => References {
    // Reference using `self.{reference_name}`
    kind: ReferenceKind::StructField,
    // Create a list of unique field names that can be referenced
    candidates: unique_named_fields(named_fields),
    },
    syn::Fields::Unnamed(_) => todo!(),
    syn::Fields::Unit => todo!(),
    };
  • replacement in fluent_embed/src/parse_macro.rs at line 77
    [4.1513][4.1513:1589]()
    expr_for_message(&group, &ident_kebab_case, ReferenceKind::StructField)
    [4.1513]
    [4.1589]
    expr_for_message(&group, &ident_kebab_case, &references)
  • replacement in fluent_embed/src/parse_macro.rs at line 83
    [4.1695][4.1695:1714]()
    ) -> TokenStream {
    [4.1695]
    [4.1714]
    ) -> Result<TokenStream, FluentError> {
  • replacement in fluent_embed/src/parse_macro.rs at line 105
    [4.83][4.2681:2777]()
    let arm_body = expr_for_message(&group, &variant_kebab_case, ReferenceKind::EnumField);
    [4.83]
    [4.2777]
    let references = match &enum_variant.fields {
    syn::Fields::Named(named_fields) => References {
    kind: ReferenceKind::EnumField,
    candidates: unique_named_fields(named_fields),
    },
    syn::Fields::Unnamed(_) => todo!(),
    syn::Fields::Unit => todo!(),
    };
    let arm_body = expr_for_message(&group, &variant_kebab_case, &references)?;
  • replacement in fluent_embed/src/parse_macro.rs at line 118
    [4.328][4.2858:2871]()
    quote! {
    [4.328]
    [4.2871]
    Ok(quote! {
  • replacement in fluent_embed/src/parse_macro.rs at line 122
    [4.2931][4.2931:2937]()
    }
    [4.2931]
    [4.1605]
    })
  • edit in fluent_embed/src/parse_fluent.rs at line 1
    [4.33]
    [4.34]
    use std::collections::HashSet;
  • replacement in fluent_embed/src/parse_fluent.rs at line 4
    [4.60][4.129:218]()
    Expression, InlineExpression, Message, Pattern, PatternElement, Variant, VariantKey,
    [4.60]
    [4.140]
    Entry, Expression, InlineExpression, Message, Pattern, PatternElement, Resource, Variant,
    VariantKey,
  • replacement in fluent_embed/src/parse_fluent.rs at line 7
    [4.143][4.143:182]()
    use heck::{ToPascalCase, ToSnakeCase};
    [4.143]
    [4.182]
    use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
    use miette::{Diagnostic, NamedSource, SourceSpan};
  • edit in fluent_embed/src/parse_fluent.rs at line 11
    [4.238]
    [4.238]
    use thiserror::Error;
    #[derive(Diagnostic, Debug, Error)]
    #[error("Field doesn't exist")]
    pub struct InvalidReference {
    #[source_code]
    src: NamedSource<String>,
    #[label("Can't find any Rust fields with this name")]
    span: SourceSpan,
    #[help]
    help: String,
    }
  • edit in fluent_embed/src/parse_fluent.rs at line 24
    [4.239]
    [4.2938]
    #[derive(Diagnostic, Debug, Error)]
    #[diagnostic(transparent)]
    #[error(transparent)]
    pub enum FluentError {
    InvalidReference(#[from] InvalidReference),
    }
  • replacement in fluent_embed/src/parse_fluent.rs at line 37
    [4.3028][4.3028:3123]()
    pub(crate) fn pattern(pattern: &Pattern<String>, reference_kind: ReferenceKind) -> syn::Expr {
    [4.3028]
    [4.352]
    #[derive(Clone, Debug)]
    pub struct References {
    pub kind: ReferenceKind,
    pub candidates: HashSet<String>,
    }
    pub(crate) fn pattern(
    message: &Message<String>,
    pattern: &Pattern<String>,
    references: &References,
    path: &str,
    ) -> Result<syn::Expr, FluentError> {
  • replacement in fluent_embed/src/parse_fluent.rs at line 56
    [4.640][4.3124:3208]()
    let expression = placeable_expression(&expression, reference_kind);
    [4.640]
    [4.714]
    let expression = match expression {
    Expression::Select { selector, variants } => {
    let target = inline_expression(message, selector, references, path)?;
    let arms: Vec<syn::Arm> = variants
    .iter()
    .map(|item| variant(message, item, references, path))
    .collect::<Result<_, FluentError>>()?;
    parse_quote! {
    match plural_rules.category_for(#target) {
    #(#arms),*
    }
    }
    }
    Expression::Inline(expression) => {
    inline_expression(message, expression, references, path)?
    }
    };
  • edit in fluent_embed/src/parse_fluent.rs at line 82
    [4.943][4.943:1018]()
    parse_quote!(format!(#format_body_literal, #(#format_arguments),*))
    }
  • replacement in fluent_embed/src/parse_fluent.rs at line 83
    [4.1019][4.3209:3312](),[4.291][4.1133:1211](),[4.356][4.1133:1211](),[4.3312][4.1133:1211](),[4.1133][4.1133:1211](),[4.1211][4.3313:3490](),[4.424][4.1367:1395](),[4.3490][4.1367:1395](),[4.1367][4.1367:1395](),[4.1395][4.3491:3550](),[4.3550][4.485:516](),[4.485][4.485:516](),[4.516][4.1498:1541](),[4.1498][4.1498:1541](),[4.1541][4.3551:3640](),[4.590][4.1620:1626](),[4.3640][4.1620:1626](),[4.1620][4.1620:1626]()
    fn placeable_expression(expression: &Expression<String>, reference_kind: ReferenceKind) -> syn::Expr {
    match expression {
    Expression::Select { selector, variants } => {
    let target = inline_expression(selector, reference_kind);
    let arms: Vec<syn::Arm> = variants.iter().map(|item| variant(item, reference_kind)).collect();
    parse_quote! {
    match plural_rules.category_for(#target) {
    #(#arms),*
    }
    }
    }
    Expression::Inline(expression) => inline_expression(expression, reference_kind),
    }
    [4.1019]
    [4.1626]
    Ok(parse_quote!(
    format!(#format_body_literal, #(#format_arguments),*)
    ))
  • replacement in fluent_embed/src/parse_fluent.rs at line 88
    [4.1629][4.3641:3747](),[4.432][4.1746:1769](),[4.3747][4.1746:1769](),[4.1746][4.1746:1769]()
    fn inline_expression(expression: &InlineExpression<String>, reference_kind: ReferenceKind) -> syn::Expr {
    match expression {
    [4.1629]
    [4.1769]
    fn inline_expression(
    message: &Message<String>,
    expression: &InlineExpression<String>,
    references: &References,
    path: &str,
    ) -> Result<syn::Expr, FluentError> {
    Ok(match expression {
  • replacement in fluent_embed/src/parse_fluent.rs at line 107
    [4.2396][4.2396:2466](),[4.2466][4.3748:3783]()
    let ident = format_ident!("{}", id.name.to_snake_case());
    match reference_kind {
    [4.2396]
    [4.3783]
    // Make sure the referenced variable is in the set of valid variables
    // let ident = format_ident!("{}", id.name.to_snake_case());
    let ident = if let Some(variable) = references.candidates.get(&id.name.to_snake_case())
    {
    format_ident!("{variable}")
    } else {
    // Create a fake `fluent_syntax::ast::Resource` to serialize into a String
    let error_resource = Resource {
    body: vec![Entry::Message(message.to_owned())],
    };
    let source_string = fluent_syntax::serializer::serialize_with_options(
    &error_resource,
    fluent_syntax::serializer::Options {
    // Make sure to include all source code in error snippet, even if marked as "junk"
    with_junk: true,
    },
    );
    let location = source_string.find(&format!("${}", id.name)).unwrap();
    return Err(FluentError::InvalidReference(InvalidReference {
    src: NamedSource::new(path, source_string.clone()),
    span: (location..location + id.name.len()).into(),
    help: format!(
    "the following references are valid:\n{}",
    references
    .candidates
    .iter()
    .map(|field| format!("- ${}", field.to_lower_camel_case()))
    .collect::<Vec<String>>()
    .join("\n")
    ),
    }));
    };
    match references.kind {
  • edit in fluent_embed/src/parse_fluent.rs at line 146
    [4.3937][4.3937:3950]()
  • replacement in fluent_embed/src/parse_fluent.rs at line 151
    [4.2584][4.2584:2590]()
    }
    [4.2584]
    [4.2590]
    })
  • replacement in fluent_embed/src/parse_fluent.rs at line 154
    [4.2593][4.3951:4034]()
    fn variant(variant: &Variant<String>, reference_kind: ReferenceKind) -> syn::Arm {
    [4.2593]
    [4.696]
    fn variant(
    message: &Message<String>,
    variant: &Variant<String>,
    references: &References,
    path: &str,
    ) -> Result<syn::Arm, FluentError> {
  • replacement in fluent_embed/src/parse_fluent.rs at line 161
    [4.746][4.4035:4091]()
    let body = pattern(&variant.value, reference_kind);
    [4.746]
    [4.2775]
    let body = pattern(message, &variant.value, references, path)?;
  • replacement in fluent_embed/src/parse_fluent.rs at line 172
    [4.3073][4.3073:3109]()
    parse_quote!(#pattern => #body)
    [4.3073]
    [4.3109]
    Ok(parse_quote!(#pattern => #body))
  • replacement in fluent_embed/src/parse_fluent.rs at line 181
    [4.3399][4.3399:3455]()
    VariantKey::NumberLiteral { value } => todo!(),
    [4.3399]
    [4.3455]
    VariantKey::NumberLiteral { .. } => todo!(),
  • replacement in fluent_embed/src/parse_fluent.rs at line 185
    [4.551][4.4092:4187]()
    pub(crate) fn message(message: &Message<String>, reference_kind: ReferenceKind) -> syn::Expr {
    [4.551]
    [4.615]
    pub(crate) fn message(
    message: &Message<String>,
    references: &References,
    path: &str,
    ) -> Result<syn::Expr, FluentError> {
  • replacement in fluent_embed/src/parse_fluent.rs at line 191
    [4.665][4.4188:4227]()
    pattern(value, reference_kind)
    [4.665]
    [4.688]
    pattern(message, value, references, path)
  • replacement in fluent_embed/src/parse_fluent.rs at line 193
    [4.701][4.701:726]()
    parse_quote!(())
    [4.701]
    [4.726]
    Ok(parse_quote!(()))
  • edit in fluent_embed/src/lib.rs at line 6
    [4.4318]
    [4.4318]
    use miette::Diagnostic;
  • edit in fluent_embed/src/lib.rs at line 10
    [4.4388]
    [4.4388]
    use thiserror::Error;
  • replacement in fluent_embed/src/lib.rs at line 13
    [4.3475][4.894:912]()
    mod parse_fluent;
    [4.3475]
    [4.1608]
    pub mod parse_fluent;
  • replacement in fluent_embed/src/lib.rs at line 16
    [3.26][3.26:61]()
    #[derive(thiserror::Error, Debug)]
    [3.26]
    [3.61]
    pub use parse_fluent::FluentError;
    #[derive(Diagnostic, Debug, Error)]
    #[error(transparent)]
  • edit in fluent_embed/src/lib.rs at line 21
    [3.87][3.87:107]()
    #[error("{0}")]
  • edit in fluent_embed/src/lib.rs at line 22
    [3.143][3.143:163]()
    #[error("{0}")]
  • replacement in fluent_embed/src/lib.rs at line 23
    [3.197][3.197:216]()
    #[error(":(")]
    [3.197]
    [3.216]
    #[error("Directory could not be matched by glob in macro attribute")]
  • edit in fluent_embed/src/lib.rs at line 28
    [3.294]
    [3.294]
    }
    #[derive(Diagnostic, Debug, Error)]
    #[diagnostic(transparent)]
    #[error(transparent)]
    pub enum MacroError {
    Attribute(#[from] AttributeError),
    #[error("Error in Fluent source code")]
    Fluent(#[from] FluentError),
  • edit in fluent_embed/src/lib.rs at line 45
    [4.4657]
    [4.4657]
    let mut paths = HashMap::new();
  • edit in fluent_embed/src/lib.rs at line 51
    [4.176]
    [3.385]
  • replacement in fluent_embed/src/lib.rs at line 68
    [4.5377][4.5377:5421]()
    resources.insert(locale, resource);
    [4.5377]
    [4.5421]
    resources.insert(locale.clone(), resource);
    paths.insert(locale, entry.to_candidate_path().to_string());
  • replacement in fluent_embed/src/lib.rs at line 72
    [4.5428][4.177:225]()
    Ok(Group::new(locale!("en-US"), resources))
    [4.5428]
    [4.5472]
    Ok(Group::new(locale!("en-US"), resources, paths))
  • replacement in fluent_embed/src/lib.rs at line 75
    [4.5475][4.226:299](),[4.299][3.703:746]()
    pub fn localize(
    path: &syn::LitStr,
    derive_input: &DeriveInput,
    ) -> Result<TokenStream, AttributeError> {
    [4.5475]
    [4.342]
    pub fn localize(path: &syn::LitStr, derive_input: &DeriveInput) -> Result<TokenStream, MacroError> {
  • replacement in fluent_embed/src/lib.rs at line 79
    [4.5639][4.5639:5738]()
    syn::Data::Struct(_struct_data) => parse_macro::derive_struct(group, &derive_input.ident),
    [4.5639]
    [4.5738]
    syn::Data::Struct(struct_data) => {
    parse_macro::derive_struct(group, &derive_input.ident, &struct_data.fields)
    }
  • replacement in fluent_embed/src/lib.rs at line 84
    [4.5870][4.5870:5877]()
    };
    [4.5870]
    [4.5877]
    }?;
  • replacement in fluent_embed/src/group.rs at line 1
    [4.3543][4.6119:6167]()
    use crate::parse_fluent::{self, ReferenceKind};
    [4.3543]
    [4.428]
    use crate::parse_fluent::{self, FluentError, References};
  • edit in fluent_embed/src/group.rs at line 42
    [4.881]
    [4.4310]
    paths: HashMap<Locale, String>,
  • replacement in fluent_embed/src/group.rs at line 46
    [4.808][4.914:1015]()
    pub fn new(canonical_locale: Locale, mut resources: HashMap<Locale, Resource<String>>) -> Self {
    [4.808]
    [4.1015]
    pub fn new(
    canonical_locale: Locale,
    mut resources: HashMap<Locale, Resource<String>>,
    paths: HashMap<Locale, String>,
    ) -> Self {
  • edit in fluent_embed/src/group.rs at line 70
    [4.1235]
    [4.1886]
    paths,
  • edit in fluent_embed/src/group.rs at line 72
    [4.553][4.6608:6614](),[4.1896][4.6608:6614](),[4.6608][4.6608:6614](),[4.6614][4.1897:1898](),[4.1898][4.6168:6373]()
    }
    fn message_column(&self, id: &str) -> usize {
    self.canonical_messages
    .iter()
    .position(|message| message.id.name == id)
    .expect("Message id must be valid")
  • replacement in fluent_embed/src/group.rs at line 78
    [4.6466][4.6466:6731]()
    pub fn canonical_message(&self, id: &str, reference_kind: ReferenceKind) -> syn::Expr {
    let message_column = self.message_column(id);
    let message = &self.canonical_messages[message_column];
    parse_fluent::message(message, reference_kind)
    [4.6466]
    [4.6731]
    pub fn canonical_message(
    &self,
    id: &str,
    references: &References,
    ) -> Result<syn::Expr, FluentError> {
    let message = self
    .canonical_messages
    .iter()
    .find(|message| message.id.name == id)
    .expect("Message id must be valid");
    let path = self.paths.get(&self.canonical_locale).unwrap();
    parse_fluent::message(message, references, path)
  • replacement in fluent_embed/src/group.rs at line 96
    [4.6789][4.6789:6936]()
    reference_kind: ReferenceKind,
    ) -> impl Iterator<Item = (&Locale, syn::Expr)> {
    let message_column = self.message_column(id);
    [4.6789]
    [4.6936]
    references: &References,
    ) -> Result<Vec<(&Locale, syn::Expr)>, FluentError> {
    let mut messages = Vec::with_capacity(self.extra_locales.len());
    let message_column = self
    .canonical_messages
    .iter()
    .position(|message| message.id.name == id)
    .expect("Message id must be valid");
  • replacement in fluent_embed/src/group.rs at line 105
    [4.6937][4.1458:1665](),[4.1458][4.1458:1665](),[4.1665][4.6938:7170](),[4.7170][4.1729:1740](),[4.1729][4.1729:1740]()
    self.extra_locales.iter().filter_map(move |locale_group| {
    locale_group
    .messages
    .get(message_column)
    .unwrap()
    .as_ref()
    .map(|message: &Message<String>| {
    (
    &locale_group.locale,
    parse_fluent::message(message, reference_kind),
    )
    })
    })
    [4.6937]
    [4.1108]
    for locale_group in &self.extra_locales {
    // Include the message only if it exists in this locale
    if let Some(message) = &locale_group.messages[message_column] {
    let path = self.paths.get(&locale_group.locale).unwrap();
    let message_expr = parse_fluent::message(&message, references, path)?;
    messages.push((&locale_group.locale, message_expr))
    }
    }
    Ok(messages)
  • replacement in fluent_embed/Cargo.toml at line 10
    [4.1410][4.1410:1435]()
    fluent-syntax = "0.11.0"
    [4.1410]
    [4.1435]
    fluent-syntax = "0.11.1"
  • edit in fluent_embed/Cargo.toml at line 13
    [4.574]
    [4.1488]
    miette = "7.2.0"
  • edit in cli_macros/src/lib.rs at line 2
    [4.130]
    [4.130]
    use fluent_embed::{AttributeError, FluentError, MacroError};
    use proc_macro::TokenStream;
    use proc_macro_error::{abort, emit_call_site_error, emit_error, proc_macro_error};
    use quote::{quote, ToTokens};
    use syn::parse_macro_input;
    fn attribute_error(error: AttributeError, derive_attribute: &syn::LitStr) {
    match error {
    AttributeError::Build(build_error) => {
    for location in build_error.locations() {
    // Create a token stream from the attribute's string literal
    let [proc_macro2::TokenTree::Literal(ref string_literal)] = derive_attribute
    .to_token_stream()
    .into_iter()
    .collect::<Vec<_>>()[..]
    else {
    abort!(derive_attribute, "unexpected macro attribute");
    };
    let (span_start, span_length) = location.span();
    let error_source = string_literal
    // Offset by 1 to skip the starting `"` double-quote character
    .subspan(span_start + 1..=span_start + span_length)
    // Fall back to the whole attribute if `subspan()` returns `None`
    // This will always happend on stable as subspan is nightly-only:
    // https://docs.rs/proc-macro2/latest/proc_macro2/struct.Literal.html#method.subspan
    .unwrap_or(derive_attribute.span());
    emit_error! { error_source, "invalid glob";
    note = location.to_string();
    };
    }
    }
    AttributeError::Walk(walk_error) => {
    // Generate help text
    let help = if let Some(path) = walk_error.path() {
    let path_name = path.to_str().unwrap();
    // Might hit an error if file exists but insufficient permissions
    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! { derive_attribute, "error at depth {} while walking path", walk_error.depth();
    help = help;
    };
    }
    AttributeError::NoMatches {
    path,
    complete_match,
    } => {
    // Validate the assumption that the user has provided an exact path
    assert!(path.exists());
    assert_eq!(path.to_string_lossy(), complete_match);
    emit_error! { derive_attribute, "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";
    };
    }
    }
    }
  • replacement in cli_macros/src/lib.rs at line 72
    [4.131][3.769:803](),[3.803][4.131:160](),[4.131][4.131:160]()
    use fluent_embed::AttributeError;
    use proc_macro::TokenStream;
    [4.131]
    [4.206]
    fn fluent_error(error: FluentError) {
    // It doesn't seem like you can reference non-Rust source files
    // in `proc_macro::Span`, so use Miette to pretty-print our own reports.
    // This includes setting up a global report handler with some extra options
    miette::set_hook(Box::new(|_| {
    Box::new(
    miette::MietteHandlerOpts::new()
    // Force color output, even when printing using the debug formatter
    .color(true)
    .build(),
    )
    }))
    .unwrap();
    match error {
    FluentError::InvalidReference(invalid_reference) => {
    eprintln!("{:?}", miette::Error::new(invalid_reference));
    }
    }
    // Make sure compilation fails
    emit_call_site_error!("invalid Fluent source code, see above for details");
    }
  • replacement in cli_macros/src/lib.rs at line 104
    [4.791][3.804:838]()
    Err(attribute_error) => {
    [4.791]
    [4.820]
    Err(macro_error) => {
  • replacement in cli_macros/src/lib.rs at line 106
    [4.868][3.839:931](),[3.931][4.143:714](),[4.143][4.143:714]()
    match attribute_error {
    AttributeError::Build(build_error) => {
    for location in build_error.locations() {
    // Create a token stream from the attribute's string literal
    let [proc_macro2::TokenTree::Literal(ref string_literal)] =
    derive_attribute
    .to_token_stream()
    .into_iter()
    .collect::<Vec<_>>()[..]
    else {
    abort!(derive_attribute, "unexpected macro attribute");
    };
    [4.868]
    [4.714]
    match macro_error {
    MacroError::Attribute(error) => attribute_error(error, &derive_attribute),
    MacroError::Fluent(error) => fluent_error(error),
    }
  • edit in cli_macros/src/lib.rs at line 111
    [4.715][4.715:1576](),[4.1576][3.932:986](),[3.986][4.1007:1911](),[4.1007][4.1007:1911](),[4.1911][3.987:1759](),[3.1759][4.1911:1926](),[4.1911][4.1911:1926](),[4.1926][4.1926:1927]()
    let (span_start, span_length) = location.span();
    let error_source = string_literal
    // Offset by 1 to skip the starting `"` double-quote character
    .subspan(span_start + 1..=span_start + span_length)
    // Fall back to the whole attribute if `subspan()` returns `None`
    // This will always happend on stable as subspan is nightly-only:
    // https://docs.rs/proc-macro2/latest/proc_macro2/struct.Literal.html#method.subspan
    .unwrap_or(derive_attribute.span());
    emit_error! { error_source, "invalid glob";
    note = location.to_string();
    };
    }
    }
    AttributeError::Walk(walk_error) => {
    // Generate help text
    let help = if let Some(path) = walk_error.path() {
    let path_name = path.to_str().unwrap();
    // Might hit an error if file exists but insufficient permissions
    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! { derive_attribute, "error at depth {} while walking path", walk_error.depth();
    help = help;
    }
    }
    AttributeError::NoMatches {
    path,
    complete_match,
    } => {
    // Validate the assumption that the user has provided an exact path
    assert!(path.exists());
    assert_eq!(path.to_string_lossy(), complete_match);
    emit_error! { derive_attribute, "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";
    };
    }
    };
  • edit in cli_macros/src/lib.rs at line 130
    [4.160][4.0:61](),[4.61][4.61:91](),[4.91][4.464:492](),[4.178][4.464:492]()
    use proc_macro_error::{abort, emit_error, proc_macro_error};
    use quote::{quote, ToTokens};
    use syn::parse_macro_input;
  • resolve order conflict in cli_macros/src/lib.rs at line 130
    [4.675]
  • edit in cli_macros/Cargo.toml at line 14
    [4.888]
    [4.2313]
    miette = { version = "7.2.0", features = ["fancy"] }
  • edit in Cargo.lock at line 4
    [27.4381]
    [4.1093]
    [[package]]
    name = "addr2line"
    version = "0.22.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
    dependencies = [
    "gimli",
    ]
  • edit in Cargo.lock at line 15
    [4.1106]
    [4.1017]
    name = "adler"
    version = "1.0.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
    [[package]]
  • edit in Cargo.lock at line 28
    [4.1230]
    [2.0]
    [[package]]
    name = "backtrace"
    version = "0.3.73"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
    dependencies = [
    "addr2line",
    "cc",
    "cfg-if",
    "libc",
    "miniz_oxide",
    "object",
    "rustc-demangle",
    ]
    [[package]]
    name = "backtrace-ext"
    version = "0.2.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
    dependencies = [
    "backtrace",
    ]
    [[package]]
    name = "bitflags"
    version = "2.6.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
  • edit in Cargo.lock at line 70
    [2.377]
    [4.1230]
    [[package]]
    name = "cfg-if"
    version = "1.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
  • edit in Cargo.lock at line 85
    [28.18]
    [4.2356]
    "miette",
  • edit in Cargo.lock at line 131
    [4.1488]
    [4.1488]
    name = "errno"
    version = "0.3.9"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
    dependencies = [
    "libc",
    "windows-sys 0.52.0",
    ]
    [[package]]
  • replacement in Cargo.lock at line 163
    [4.1660][4.1660:1679]()
    version = "0.11.0"
    [4.1660]
    [4.1679]
    version = "0.11.1"
  • replacement in Cargo.lock at line 165
    [4.1744][4.1744:1822]()
    checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
    [4.1744]
    [4.1822]
    checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
  • edit in Cargo.lock at line 177
    [4.589]
    [29.283]
    "miette",
  • edit in Cargo.lock at line 207
    [27.4394]
    [4.1989]
    name = "gimli"
    version = "0.29.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
    [[package]]
  • edit in Cargo.lock at line 302
    [4.1963]
    [4.1963]
    [[package]]
    name = "is_ci"
    version = "1.2.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
  • edit in Cargo.lock at line 329
    [28.209]
    [4.3670]
    [[package]]
    name = "linux-raw-sys"
    version = "0.4.14"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
  • edit in Cargo.lock at line 381
    [4.2389]
    [4.2389]
    name = "miette"
    version = "7.2.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1"
    dependencies = [
    "backtrace",
    "backtrace-ext",
    "cfg-if",
    "miette-derive",
    "owo-colors",
    "supports-color",
    "supports-hyperlinks",
    "supports-unicode",
    "terminal_size",
    "textwrap",
    "thiserror",
    "unicode-width",
    ]
    [[package]]
    name = "miette-derive"
    version = "7.2.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
    dependencies = [
    "proc-macro2",
    "quote",
    "syn 2.0.48",
    ]
    [[package]]
  • edit in Cargo.lock at line 416
    [4.2575]
    [4.2575]
    [[package]]
    name = "miniz_oxide"
    version = "0.7.4"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
    dependencies = [
    "adler",
    ]
  • edit in Cargo.lock at line 466
    [4.3874]
    [30.343]
    name = "object"
    version = "0.36.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
    dependencies = [
    "memchr",
    ]
    [[package]]
  • edit in Cargo.lock at line 484
    [27.4501]
    [4.2813]
    name = "owo-colors"
    version = "4.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
    [[package]]
  • edit in Cargo.lock at line 578
    [4.3762]
    [4.3762]
    [[package]]
    name = "rustc-demangle"
    version = "0.1.24"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
    [[package]]
    name = "rustix"
    version = "0.38.34"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
    dependencies = [
    "bitflags",
    "errno",
    "libc",
    "linux-raw-sys",
    "windows-sys 0.52.0",
    ]
  • edit in Cargo.lock at line 634
    [4.4546]
    [4.4546]
    name = "smawk"
    version = "0.3.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
    [[package]]
  • edit in Cargo.lock at line 644
    [4.4735]
    [4.4735]
    [[package]]
    name = "supports-color"
    version = "3.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f"
    dependencies = [
    "is_ci",
    ]
    [[package]]
    name = "supports-hyperlinks"
    version = "3.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee"
    [[package]]
    name = "supports-unicode"
    version = "3.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
  • edit in Cargo.lock at line 705
    [4.2237]
    [4.2237]
    name = "terminal_size"
    version = "0.3.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
    dependencies = [
    "rustix",
    "windows-sys 0.48.0",
    ]
    [[package]]
    name = "textwrap"
    version = "0.16.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
    dependencies = [
    "smawk",
    "unicode-linebreak",
    "unicode-width",
    ]
    [[package]]
  • edit in Cargo.lock at line 760
    [27.5395]
    [4.2708]
    [[package]]
    name = "unicode-linebreak"
    version = "0.1.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
  • edit in Cargo.lock at line 772
    [4.2913]
    [4.3989]
    [[package]]
    name = "unicode-width"
    version = "0.1.13"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
  • edit in Cargo.lock at line 846
    [4.5622]
    [4.5622]
    [[package]]
    name = "windows-sys"
    version = "0.48.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
    dependencies = [
    "windows-targets 0.48.5",
    ]
  • edit in Cargo.lock at line 857
    [4.5635]
    [4.5251]
    name = "windows-sys"
    version = "0.52.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
    dependencies = [
    "windows-targets 0.52.6",
    ]
    [[package]]
    name = "windows-targets"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
    dependencies = [
    "windows_aarch64_gnullvm 0.48.5",
    "windows_aarch64_msvc 0.48.5",
    "windows_i686_gnu 0.48.5",
    "windows_i686_msvc 0.48.5",
    "windows_x86_64_gnu 0.48.5",
    "windows_x86_64_gnullvm 0.48.5",
    "windows_x86_64_msvc 0.48.5",
    ]
    [[package]]
    name = "windows-targets"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
    dependencies = [
    "windows_aarch64_gnullvm 0.52.6",
    "windows_aarch64_msvc 0.52.6",
    "windows_i686_gnu 0.52.6",
    "windows_i686_gnullvm",
    "windows_i686_msvc 0.52.6",
    "windows_x86_64_gnu 0.52.6",
    "windows_x86_64_gnullvm 0.52.6",
    "windows_x86_64_msvc 0.52.6",
    ]
    [[package]]
    name = "windows_aarch64_gnullvm"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
    [[package]]
    name = "windows_aarch64_gnullvm"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
    [[package]]
    name = "windows_aarch64_msvc"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
    [[package]]
    name = "windows_aarch64_msvc"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
    [[package]]
    name = "windows_i686_gnu"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
    [[package]]
    name = "windows_i686_gnu"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
    [[package]]
    name = "windows_i686_gnullvm"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
    [[package]]
    name = "windows_i686_msvc"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
    [[package]]
    name = "windows_i686_msvc"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
    [[package]]
    name = "windows_x86_64_gnu"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
    [[package]]
    name = "windows_x86_64_gnu"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
    [[package]]
    name = "windows_x86_64_gnullvm"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
    [[package]]
    name = "windows_x86_64_gnullvm"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
    [[package]]
    name = "windows_x86_64_msvc"
    version = "0.48.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
    [[package]]
    name = "windows_x86_64_msvc"
    version = "0.52.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
    [[package]]