poexam 0.0.10

Blazingly fast PO linter.
// SPDX-FileCopyrightText: 2026 Sébastien Helleu <flashcode@flashtux.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Implementation of the `encoding` rule: check incorrect encoding.

use crate::checker::Checker;
use crate::diagnostic::{Diagnostic, Severity};
use crate::po::entry::Entry;
use crate::rules::rule::RuleChecker;

pub struct EncodingRule;

impl RuleChecker for EncodingRule {
    fn name(&self) -> &'static str {
        "encoding"
    }

    fn description(&self) -> &'static str {
        "Check for invalid characters based on declared encoding."
    }

    fn is_default(&self) -> bool {
        true
    }

    fn is_check(&self) -> bool {
        true
    }

    /// Check for translation with incorrect encoding.
    ///
    /// The encoding used to check is the one declared in the PO file, with a fallback
    /// to UTF-8 if not specified, example:
    /// ```text
    /// "Content-Type: text/plain; charset=UTF-8\n"
    /// ```
    ///
    /// Wrong entry:
    /// ```text
    /// msgid "tested"
    /// msgstr "test�"
    /// ```
    ///
    /// Correct entry:
    /// ```text
    /// msgid "tested"
    /// msgstr "testé"
    /// ```
    ///
    /// Diagnostics reported:
    /// - [`error`](Severity::Error): `invalid characters for encoding …`
    fn check_entry(&self, checker: &Checker, entry: &Entry) -> Vec<Diagnostic> {
        if entry.encoding_error {
            self.new_diag(
                checker,
                Severity::Error,
                format!(
                    "invalid characters for encoding {}",
                    checker.encoding_name()
                ),
            )
            .map(|d| d.with_entry(entry))
            .into_iter()
            .collect()
        } else {
            vec![]
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{diagnostic::Diagnostic, rules::rule::Rules};

    fn check_encoding(content: &str) -> Vec<Diagnostic> {
        let mut checker = Checker::new(content.as_bytes());
        let rules = Rules::new(vec![Box::new(EncodingRule {})]);
        checker.do_all_checks(&rules);
        checker.diagnostics
    }

    #[test]
    fn test_encoding_ok() {
        let diags = check_encoding(
            r#"
msgid "tested"
msgstr "testé"
"#,
        );
        assert!(diags.is_empty());
    }

    #[test]
    fn test_encoding_error_noqa() {
        let mut checker =
            Checker::new(b"#, noqa:encoding\nmsgid \"tested\"\nmsgstr \"test\xe9\"\n");
        let rules = Rules::new(vec![Box::new(EncodingRule {})]);
        checker.do_all_checks(&rules);
        let diags = checker.diagnostics;
        assert!(diags.is_empty());
    }

    #[test]
    fn test_encoding_error() {
        let mut checker = Checker::new(b"msgid \"tested\"\nmsgstr \"test\xe9\"\n");
        let rules = Rules::new(vec![Box::new(EncodingRule {})]);
        checker.do_all_checks(&rules);
        let diags = checker.diagnostics;
        assert_eq!(diags.len(), 1);
        let diag = &diags[0];
        assert_eq!(diag.severity, Severity::Error);
        assert_eq!(diag.message, "invalid characters for encoding UTF-8");
    }
}