use crate::Severity;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct DiagnosticCode {
namespace: &'static str,
number: u32,
}
impl DiagnosticCode {
#[must_use]
pub const fn namespace(self) -> &'static str {
self.namespace
}
#[must_use]
pub const fn number(self) -> u32 {
self.number
}
pub(crate) const fn new(namespace: &'static str, number: u32) -> Self {
Self { namespace, number }
}
}
impl std::fmt::Display for DiagnosticCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{:04}", self.namespace, self.number)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum DiagnosticCategory {
Syntax,
Semantic,
Layout,
Text,
Pdf,
Io,
Internal,
}
impl std::fmt::Display for DiagnosticCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Syntax => "Syntax",
Self::Semantic => "Semantic",
Self::Layout => "Layout",
Self::Text => "Text",
Self::Pdf => "Pdf",
Self::Io => "Io",
Self::Internal => "Internal",
};
f.write_str(s)
}
}
#[derive(Debug)]
pub struct DiagnosticDef {
code: DiagnosticCode,
slug: &'static str,
default_severity: Severity,
category: DiagnosticCategory,
owner: &'static str,
summary: &'static str,
}
impl DiagnosticDef {
#[must_use]
pub const fn code(&self) -> DiagnosticCode {
self.code
}
#[must_use]
pub const fn slug(&self) -> &'static str {
self.slug
}
#[must_use]
pub const fn default_severity(&self) -> Severity {
self.default_severity
}
#[must_use]
pub const fn category(&self) -> DiagnosticCategory {
self.category
}
#[must_use]
pub const fn owner(&self) -> &'static str {
self.owner
}
#[must_use]
pub const fn summary(&self) -> &'static str {
self.summary
}
pub(crate) const fn new(
code: DiagnosticCode,
slug: &'static str,
default_severity: Severity,
category: DiagnosticCategory,
owner: &'static str,
summary: &'static str,
) -> Self {
Self {
code,
slug,
default_severity,
category,
owner,
summary,
}
}
}
macro_rules! define_codes {
(
$(
$(#[$meta:meta])*
$name:ident = $num:literal, $sev:ident, $cat:ident, $slug:literal, $owner:literal, $summary:literal;
)*
) => {
$(
$(#[$meta])*
pub static $name: DiagnosticDef = DiagnosticDef::new(
DiagnosticCode::new("MOS", $num),
$slug,
Severity::$sev,
DiagnosticCategory::$cat,
$owner,
$summary,
);
)*
pub static ALL: &[&DiagnosticDef] = &[ $( &$name ),* ];
#[cfg(test)]
mod generated_tests {
use super::*;
#[test]
fn numbers_are_globally_unique() {
let mut seen = std::collections::BTreeSet::new();
for def in ALL {
let key = (def.code().namespace(), def.code().number());
assert!(
seen.insert(key),
"duplicate diagnostic number: {}",
def.code()
);
}
}
#[test]
fn slugs_are_unique_and_kebab_case() {
let mut seen = std::collections::BTreeSet::new();
for def in ALL {
assert!(seen.insert(def.slug()), "duplicate slug: {}", def.slug());
assert!(
!def.slug().is_empty()
&& def.slug().bytes().all(|b| {
b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-'
}),
"slug {:?} must be non-empty kebab-case",
def.slug()
);
}
}
#[test]
fn rendered_code_is_namespace_plus_four_digits() {
for def in ALL {
let rendered = def.code().to_string();
assert!(rendered.starts_with("MOS"), "code {rendered} must start with MOS");
assert_eq!(rendered.len(), 7, "code {rendered} must be MOS + 4 digits");
assert!(
rendered[3..].bytes().all(|b| b.is_ascii_digit()),
"code {rendered} tail must be all digits"
);
}
}
#[test]
fn static_name_matches_rendered_code() {
$(
assert_eq!(
stringify!($name),
$name.code().to_string(),
"the static's name must equal its rendered code"
);
)*
}
}
};
}
define_codes! {
MOS0010 = 10, Error, Syntax, "set-missing-identifier", "mos-parse",
"syntax: #set not followed by an identifier";
MOS0013 = 13, Error, Syntax, "directive-missing-paren", "mos-parse",
"syntax: directive missing opening parenthesis";
MOS0016 = 16, Error, Syntax, "directive-unterminated", "mos-parse",
"syntax: unterminated directive block";
MOS0019 = 19, Error, Syntax, "directive-trailing-content", "mos-parse",
"syntax: unexpected trailing content after directive";
MOS0022 = 22, Error, Syntax, "directive-malformed-arg", "mos-parse",
"syntax: malformed directive argument value";
MOS0025 = 25, Error, Syntax, "arglist-shape", "mos-parse",
"syntax: malformed argument list";
MOS0028 = 28, Warning, Syntax, "unterminated-strong", "mos-parse",
"syntax: unterminated **strong** run; treated as text";
MOS0031 = 31, Warning, Syntax, "unterminated-emphasis", "mos-parse",
"syntax: unterminated *emphasis* run; treated as text";
MOS0034 = 34, Warning, Syntax, "unterminated-code", "mos-parse",
"syntax: unterminated `code` run; treated as text";
MOS0036 = 36, Warning, Syntax, "stray-at-sign", "mos-parse",
"syntax: stray @ not followed by a label; treated as text";
MOS0038 = 38, Warning, Syntax, "lone-trailing-backslash", "mos-parse",
"syntax: lone trailing backslash at end of input; treated as text";
MOS0039 = 39, Warning, Syntax, "malformed-citation", "mos-parse",
"syntax: malformed citation group; treated as text";
MOS0043 = 43, Error, Syntax, "bibtex-parse-failed", "mos-bib",
"syntax: BibTeX database could not be parsed";
MOS0044 = 44, Error, Syntax, "csl-parse-failed", "mos-csl",
"syntax: CSL style could not be parsed";
MOS0011 = 11, Error, Semantic, "set-unknown-target", "mos-eval",
"semantic: unknown #set target";
MOS0015 = 15, Error, Semantic, "unknown-kwarg", "mos-eval",
"semantic: unknown keyword argument";
MOS0020 = 20, Error, Semantic, "arg-type-mismatch", "mos-eval",
"semantic: argument type mismatch or non-positive length";
MOS0024 = 24, Error, Semantic, "set-positional-rejected", "mos-eval",
"semantic: #set rejects positional argument";
MOS0027 = 27, Warning, Semantic, "set-sanity-floor", "mos-eval",
"semantic: #set value trips a sanity floor; value still applied";
MOS0030 = 30, Error, Semantic, "label-duplicate", "mos-eval",
"semantic: label declared more than once";
MOS0033 = 33, Error, Semantic, "label-missing", "mos-eval",
"semantic: @reference to a label that does not exist";
MOS0037 = 37, Error, Semantic, "image-missing-path", "mos-eval",
"semantic: #image/#figure missing a path argument";
MOS0040 = 40, Error, Semantic, "bibliography-missing-path", "mos-eval",
"semantic: #bibliography missing a path argument";
MOS0042 = 42, Error, Semantic, "bibliography-duplicate-path", "mos-eval",
"semantic: #bibliography path argument declared more than once";
MOS0012 = 12, Error, Io, "image-read-failed", "mos-eval",
"io: image file cannot be read from disk";
MOS0029 = 29, Error, Io, "image-decode-failed", "mos-eval",
"io: image file cannot be decoded";
MOS0041 = 41, Warning, Io, "bibliography-source-missing", "mos-eval",
"io: declared bibliography source file not found";
MOS0017 = 17, Error, Layout, "paper-size-unknown", "mos-layout",
"layout: unknown paper size";
MOS0023 = 23, Error, Layout, "geometry-breaks-page", "mos-layout",
"layout: value breaks page geometry; previous value retained";
MOS0035 = 35, Warning, Layout, "image-skipped-no-pixels", "mos-layout",
"layout: image reached layout without decoded pixels; skipped";
MOS0018 = 18, Notice, Text, "font-family-substituted", "mos-fonts",
"text: substituted bundled Noto Sans for unknown font family";
MOS0032 = 32, Warning, Text, "glyph-budget-exhausted", "mos-pdf",
"text: Base-14 /Differences glyph budget exhausted";
MOS0014 = 14, Error, Pdf, "pdf-io-failed", "mos-pdf",
"pdf: backend I/O failure";
MOS0026 = 26, Error, Pdf, "font-subset-failed", "mos-pdf",
"pdf: font subsetting failure for an embedded face";
MOS0021 = 21, Error, Internal, "internal-missing-font-plan", "mos-pdf",
"internal: missing embedded font plan for a shaped run";
}