alpm_lint/lint_rules/source_info/
unknown_architecture.rs

1//! Ensures that all [alpm-architecture] names specified in the [SRCINFO] are known.
2//!
3//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
4//! [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
5
6use std::collections::BTreeMap;
7
8use alpm_types::{Architecture, SystemArchitecture};
9use documented::Documented;
10use strum::VariantNames;
11
12use crate::{
13    internal_prelude::*,
14    issue::SourceInfoIssue,
15    lint_rules::source_info::source_info_from_resource,
16    utils::EditDistance,
17};
18
19/// # What it does?
20///
21/// Ensures that all [alpm-architecture] names specified in the [SRCINFO] are known.
22///
23/// Any string passing the [alpm-architecture] requirements is a valid architecture. However,
24/// in most cases, only a limited set of architecture names is used in practice.
25///
26/// This lint rule checks whether any uncommon architecture names are used in the [SRCINFO] which
27/// often indicates a typo.
28///
29/// Warnings emitted by this lint rule can be safely ignored if the specified architecture name is
30/// correct, but simply uncommon.
31///
32/// # Why is this bad?
33///
34/// Using an unknown architecture name is often a sign of a typo.
35///
36/// # Example
37///
38/// ```ini,ignore
39/// pkgbase = test
40///     pkgver = 1.0.0
41///     pkgrel = 1
42///     arch = 86_64
43/// ```
44///
45/// Use instead:
46///
47/// ```ini,ignore
48/// pkgbase = test
49///     pkgver = 1.0.0
50///     pkgrel = 1
51///     arch = x86_64
52/// ```
53///
54/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
55/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
56#[derive(Clone, Debug, Documented)]
57pub struct UnknownArchitecture {}
58
59impl UnknownArchitecture {
60    /// The maximum edit distance to suggest a similar architecture name.
61    const EDIT_DISTANCE_THRESHOLD: usize = 3;
62
63    /// Create a new, boxed instance of [`UnknownArchitecture`].
64    pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
65        Box::new(Self {})
66    }
67
68    /// Get all known architecture variant names, including 'any'.
69    fn known_variants() -> Vec<&'static str> {
70        let mut known = vec!["any"];
71        known.append(
72            &mut SystemArchitecture::VARIANTS
73                .iter()
74                .filter_map(|v| if *v != "unknown" { Some(*v) } else { None })
75                .collect::<Vec<_>>(),
76        );
77        known
78    }
79}
80
81impl LintRule for UnknownArchitecture {
82    fn name(&self) -> &'static str {
83        "unknown_architecture"
84    }
85
86    fn scope(&self) -> LintScope {
87        LintScope::SourceInfo
88    }
89
90    fn documentation(&self) -> String {
91        UnknownArchitecture::DOCS.into()
92    }
93
94    fn help_text(&self) -> String {
95        let known = Self::known_variants()
96            .iter()
97            .map(|arch| format!("- {}", arch))
98            .collect::<Vec<_>>()
99            .join("\n");
100
101        format!(
102            "If you are certain that the architecture name is correct, \
103        you can ignore this warning. \n\
104        Known values include: \n{known}"
105        )
106    }
107
108    fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
109        let source_info = source_info_from_resource(resources, self.scoped_name())?;
110
111        for arch in &source_info.base.architectures {
112            if let Architecture::Some(SystemArchitecture::Unknown(arch)) = arch {
113                let closest_match = Self::known_variants()
114                    .iter()
115                    .map(|known| (*known, arch.to_string().edit_distance(&known.to_string())))
116                    .min_by_key(|(_, dist)| *dist);
117
118                let suggestion = if let Some((closest, distance)) = closest_match
119                    && distance <= Self::EDIT_DISTANCE_THRESHOLD
120                {
121                    Some(format!("\nDid you mean '{closest}'?"))
122                } else {
123                    None
124                };
125
126                issues.push(LintIssue::from_rule(
127                    self,
128                    SourceInfoIssue::Generic {
129                        summary: "Uncommon architecture specified - possible typo.".to_string(),
130                        arrow_line: None,
131                        message: format!(
132                            "The architecture '{}' is not common. \
133                            This is allowed, but may indicate a typo. \
134                            {}",
135                            arch,
136                            suggestion.unwrap_or_default()
137                        ),
138                    }
139                    .into(),
140                ));
141            }
142        }
143
144        Ok(())
145    }
146
147    fn extra_links(&self) -> Option<BTreeMap<String, String>> {
148        let mut links = BTreeMap::new();
149        links.insert(
150            "SRCINFO specification".to_string(),
151            "https://alpm.archlinux.page/specifications/SRCINFO.5.html".to_string(),
152        );
153        links.insert(
154            "alpm-architecture specification".to_string(),
155            "https://alpm.archlinux.page/specifications/alpm-architecture.7.html".to_string(),
156        );
157
158        Some(links)
159    }
160}