Use Fluent AST when reporting error spans

finchie
Apr 15, 2025, 8:54 AM
7X4MEZJUMLYYIBV7ANLADELOZ7I7AJ5CKFAR35CJ2SBZDDVJFZOQC

Dependencies

  • [2] VQBJBFEX Improve error handling for missing Fluent messages
  • [3] 2SITVDYW Handle common errors in Fluent code
  • [4] 3NMKD6I5 Refactor `Localize` trait to use `std::io::Write`
  • [5] D652S2N3 Rename `parse` module to `parse_fluent`
  • [6] F5LG7WEN Emit compilation errors from Fluent source code
  • [7] NO3PDO7P Refactor `fluent_embed` to support structs
  • [8] CESJ4CTO Move macro-specific code into `macro_impl` module
  • [9] C6W7N6N5 Implement `Localize` for `FixedDecimal` and primitive number types
  • [10] 7M4UI3TW Update dependencies to latest versions
  • [11] XGRU7WZE Add `expand` feature for proc-macro debugging
  • [12] HJMYJDC7 Simplify `fluent_embed::group` module
  • [13] 5TEX4MNU Split `fluent_embed` into `group` and `parse` modules
  • [14] QFPQZR4K Refactor `fluent_embed`
  • [15] GJMBIJOE Migrate to latest env_preferences version
  • [16] 7FYXVNAB Ignore comments in Fluent source code
  • [*] VZYZRAO4 Move `output-macros` crate into workspace
  • [*] UKFEFT6L Create basic `Output` proc-macro

Change contents

  • replacement in fluent_embed_derive/src/fluent/mod.rs at line 5
    [3.1083][3.1083:1144]()
    use fluent_syntax::ast::{Entry, Message, Pattern, Resource};
    [3.1083]
    [3.95]
    use fluent_syntax::ast::{Entry, Message, Pattern};
  • edit in fluent_embed_derive/src/fluent/mod.rs at line 58
    [3.1595][3.1595:1623]()
    root_id: &'context str,
  • replacement in fluent_embed_derive/src/fluent/mod.rs at line 64
    [3.1663][3.1663:1737]()
    path: PathBuf,
    syntax_tree: fluent_syntax::ast::Resource<String>,
    [3.1663]
    [3.1737]
    named_source: NamedSource<String>,
  • edit in fluent_embed_derive/src/fluent/mod.rs at line 72
    [3.1625]
    [3.1625]
    let named_source = NamedSource::new(path.to_string_lossy(), file_contents.clone());
  • replacement in fluent_embed_derive/src/fluent/mod.rs at line 111
    [3.2908][3.2908:2951]()
    path,
    syntax_tree,
    [3.2908]
    [3.2951]
    named_source,
  • edit in fluent_embed_derive/src/fluent/mod.rs at line 116
    [3.2397][3.2397:3886](),[3.3886][3.2984:2991](),[3.2984][3.2984:2991]()
    /// Calculate the byte offset of the serialized
    fn source_with_message_offset(&self, id: &str) -> (NamedSource<String>, usize) {
    // Find the message position in the AST
    let ast_index = self
    .syntax_tree
    .body
    .iter()
    .position(|entry| {
    if let Entry::Message(message) = entry {
    message.id.name == id
    } else {
    false
    }
    })
    .unwrap();
    let options = fluent_syntax::serializer::Options {
    // Make sure to include all source code in error snippet, even if marked as "junk"
    with_junk: true,
    };
    // Serialize everything before this message to get the byte offset
    let source_before_message = fluent_syntax::serializer::serialize_with_options(
    &Resource {
    body: self.syntax_tree.body[0..ast_index].to_vec(),
    },
    options,
    );
    let byte_offset = source_before_message.len();
    let source_after_offset = fluent_syntax::serializer::serialize_with_options(
    &Resource {
    body: self.syntax_tree.body[ast_index..].to_vec(),
    },
    options,
    );
    let source = format!("{source_before_message}{source_after_offset}");
    let named_source = NamedSource::new(self.path.to_string_lossy(), source);
    (named_source, byte_offset)
    }
  • edit in fluent_embed_derive/src/fluent/mod.rs at line 128
    [2.1591][3.3459:3460](),[3.3459][3.3459:3460](),[3.3460][2.1592:1808](),[2.1808][3.3937:3938](),[3.3937][3.3937:3938](),[3.3938][2.1809:2272]()
    // fn remove_expression(
    // &mut self,
    // id: &str,
    // derive_context: &derive::Context,
    // ) -> Result<syn::Expr, Error> {
    // let message = self.messages.remove(id).unwrap();
    // let message_context = MessageContext {
    // source: &self,
    // root_id: id,
    // // Any message where this value is `None` shouldn't be accessed directly, see:
    // // https://docs.rs/fluent-syntax/latest/fluent_syntax/ast/struct.Attribute.html#example
    // pattern: message.value.as_ref().unwrap(),
    // derive_context,
    // };
    // ast::message_body(message_context)
    // }
  • replacement in fluent_embed_derive/src/fluent/group.rs at line 73
    [3.4874][3.4874:5022]()
    let mut unexpected_keys = source_file
    .message_ids()
    .filter(|id: &&str| !canonical_keys.contains(id));
    [3.4874]
    [3.5022]
    let mut unexpected_keys =
    source_file
    .messages
    .iter()
    .filter(|(message_id, _message_value)| {
    !canonical_keys.contains(&message_id.as_str())
    });
  • replacement in fluent_embed_derive/src/fluent/group.rs at line 81
    [3.5023][3.5023:5190]()
    if let Some(unexpected_key) = unexpected_keys.next() {
    let (src, message_offset) = source_file.source_with_message_offset(unexpected_key);
    [3.5023]
    [2.3167]
    // TODO: group all unexpected keys into single error
    if let Some((unexpected_key, unexpected_message)) = unexpected_keys.next() {
    let source_code = source_file.named_source.clone();
  • replacement in fluent_embed_derive/src/fluent/group.rs at line 89
    [3.5420][3.5420:5546]()
    source_code: src,
    span: SourceSpan::new(message_offset.into(), unexpected_key.len()),
    [3.5420]
    [2.3243]
    source_code,
    span: SourceSpan::from(unexpected_message.id.span.0.clone()),
  • edit in fluent_embed_derive/src/fluent/group.rs at line 123
    [2.3856][2.3856:3882]()
    root_id: &id,
  • edit in fluent_embed_derive/src/fluent/group.rs at line 147
    [2.4714][2.4714:4748]()
    root_id: &id,
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 19
    [3.500][3.974:1029]()
    PatternElement::TextElement { value } => {
    [3.500]
    [3.1029]
    PatternElement::TextElement { value, span: _ } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 23
    [3.1233][3.582:640](),[3.582][3.582:640]()
    PatternElement::Placeable { expression } => {
    [3.1233]
    [3.1234]
    PatternElement::Placeable {
    expression,
    span: _,
    } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 28
    [3.1292][3.3219:3286](),[3.3219][3.3219:3286]()
    Expression::Select { selector, variants } => {
    [3.1292]
    [3.1293]
    Expression::Select { selector, variants, span: _ } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 71
    [3.3857][3.3857:3913]()
    Expression::Inline(expression) => {
    [3.3857]
    [3.2883]
    Expression::Inline(expression, _span) => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 92
    [3.4333][3.1769:1824](),[3.1769][3.1769:1824]()
    InlineExpression::StringLiteral { value } => {
    [3.4333]
    [3.3150]
    InlineExpression::StringLiteral { value, span: _ } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 96
    [3.1946][3.1946:2001]()
    InlineExpression::NumberLiteral { value } => {
    [3.1946]
    [3.1704]
    InlineExpression::NumberLiteral { value, span: _ } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 104
    [3.2340][3.2340:2396]()
    InlineExpression::VariableReference { id } => {
    [3.2340]
    [3.4334]
    InlineExpression::VariableReference { id, span } => {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 116
    [3.4668][3.5701:5863](),[3.5863][3.5265:5266](),[3.5265][3.5265:5266](),[3.5266][3.5864:5925](),[3.5925][3.3418:3472](),[3.3472][3.5977:6037](),[3.5977][3.5977:6037]()
    let (source_code, offset) = message_context
    .source
    .source_with_message_offset(message_context.root_id);
    let location = source_code.inner()[offset..]
    .find(&format!("${fluent_name}"))
    .unwrap()
    + offset;
    [3.4668]
    [3.5352]
    let source_code = message_context.source.named_source.clone();
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 122
    [3.6230][3.3559:3638]()
    span: SourceSpan::new(location.into(), fluent_name.len()),
    [3.6230]
    [3.6305]
    span: SourceSpan::from(span.0.to_owned()),
  • replacement in Cargo.toml at line 14
    [3.5875][3.5875:5900]()
    fluent-syntax = "0.11.1"
    [3.5875]
    [3.5900]
    fluent-syntax = { git = "https://github.com/Ertanic/fluent-rs", branch = "span", features = [
    "spans",
    ] }
  • replacement in Cargo.lock at line 272
    [2.8859][2.8859:9002]()
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
    [2.8859]
    [2.9002]
    source = "git+https://github.com/Ertanic/fluent-rs?branch=span#27615271d3f8d990e935d3c99053329863d6129e"
  • replacement in Cargo.lock at line 274
    [2.9019][2.9019:9040]()
    "thiserror 1.0.69",
    [2.9019]
    [2.9040]
    "memchr",
    "thiserror 2.0.12",