-
Notifications
You must be signed in to change notification settings - Fork 13k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of #106427 - mejrs:translation_errors, r=davidtwco
Improve fluent error messages These have been really frustrating me while migrating diagnostics.
- Loading branch information
Showing
7 changed files
with
404 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use rustc_error_messages::{ | ||
fluent_bundle::resolver::errors::{ReferenceKind, ResolverError}, | ||
FluentArgs, FluentError, | ||
}; | ||
use std::borrow::Cow; | ||
use std::error::Error; | ||
use std::fmt; | ||
|
||
#[derive(Debug)] | ||
pub enum TranslateError<'args> { | ||
One { | ||
id: &'args Cow<'args, str>, | ||
args: &'args FluentArgs<'args>, | ||
kind: TranslateErrorKind<'args>, | ||
}, | ||
Two { | ||
primary: Box<TranslateError<'args>>, | ||
fallback: Box<TranslateError<'args>>, | ||
}, | ||
} | ||
|
||
impl<'args> TranslateError<'args> { | ||
pub fn message(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { | ||
Self::One { id, args, kind: TranslateErrorKind::MessageMissing } | ||
} | ||
pub fn primary(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { | ||
Self::One { id, args, kind: TranslateErrorKind::PrimaryBundleMissing } | ||
} | ||
pub fn attribute( | ||
id: &'args Cow<'args, str>, | ||
args: &'args FluentArgs<'args>, | ||
attr: &'args str, | ||
) -> Self { | ||
Self::One { id, args, kind: TranslateErrorKind::AttributeMissing { attr } } | ||
} | ||
pub fn value(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { | ||
Self::One { id, args, kind: TranslateErrorKind::ValueMissing } | ||
} | ||
|
||
pub fn fluent( | ||
id: &'args Cow<'args, str>, | ||
args: &'args FluentArgs<'args>, | ||
errs: Vec<FluentError>, | ||
) -> Self { | ||
Self::One { id, args, kind: TranslateErrorKind::Fluent { errs } } | ||
} | ||
|
||
pub fn and(self, fallback: TranslateError<'args>) -> TranslateError<'args> { | ||
Self::Two { primary: Box::new(self), fallback: Box::new(fallback) } | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum TranslateErrorKind<'args> { | ||
MessageMissing, | ||
PrimaryBundleMissing, | ||
AttributeMissing { attr: &'args str }, | ||
ValueMissing, | ||
Fluent { errs: Vec<FluentError> }, | ||
} | ||
|
||
impl fmt::Display for TranslateError<'_> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
use TranslateErrorKind::*; | ||
|
||
match self { | ||
Self::One { id, args, kind } => { | ||
writeln!(f, "failed while formatting fluent string `{id}`: ")?; | ||
match kind { | ||
MessageMissing => writeln!(f, "message was missing")?, | ||
PrimaryBundleMissing => writeln!(f, "the primary bundle was missing")?, | ||
AttributeMissing { attr } => { | ||
writeln!(f, "the attribute `{attr}` was missing")?; | ||
writeln!(f, "help: add `.{attr} = <message>`")?; | ||
} | ||
ValueMissing => writeln!(f, "the value was missing")?, | ||
Fluent { errs } => { | ||
for err in errs { | ||
match err { | ||
FluentError::ResolverError(ResolverError::Reference( | ||
ReferenceKind::Message { id, .. } | ||
| ReferenceKind::Variable { id, .. }, | ||
)) => { | ||
if args.iter().any(|(arg_id, _)| arg_id == id) { | ||
writeln!( | ||
f, | ||
"argument `{id}` exists but was not referenced correctly" | ||
)?; | ||
writeln!(f, "help: try using `{{${id}}}` instead")?; | ||
} else { | ||
writeln!( | ||
f, | ||
"the fluent string has an argument `{id}` that was not found." | ||
)?; | ||
let vars: Vec<&str> = | ||
args.iter().map(|(a, _v)| a).collect(); | ||
match &*vars { | ||
[] => writeln!(f, "help: no arguments are available")?, | ||
[one] => writeln!( | ||
f, | ||
"help: the argument `{one}` is available" | ||
)?, | ||
[first, middle @ .., last] => { | ||
write!(f, "help: the arguments `{first}`")?; | ||
for a in middle { | ||
write!(f, ", `{a}`")?; | ||
} | ||
writeln!(f, " and `{last}` are available")?; | ||
} | ||
} | ||
} | ||
} | ||
_ => writeln!(f, "{err}")?, | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// If someone cares about primary bundles, they'll probably notice it's missing | ||
// regardless or will be using `debug_assertions` | ||
// so we skip the arm below this one to avoid confusing the regular user. | ||
Self::Two { primary: box Self::One { kind: PrimaryBundleMissing, .. }, fallback } => { | ||
fmt::Display::fmt(fallback, f)?; | ||
} | ||
Self::Two { primary, fallback } => { | ||
writeln!( | ||
f, | ||
"first, fluent formatting using the primary bundle failed:\n {primary}\n \ | ||
while attempting to recover by using the fallback bundle instead, another error occurred:\n{fallback}" | ||
)?; | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Error for TranslateError<'_> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.