Skip to main content

light_fw_cli_core/
resolve.rs

1use std::collections::BTreeSet;
2
3use crate::registry::ComponentRegistry;
4use crate::scan::ScanResult;
5
6/// Компонент, необходимый проекту (используется в шаблонах).
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct RequiredComponent {
9    /// Имя крейта-компонента.
10    pub name: String,
11}
12
13/// Определяет компоненты, необходимые по результатам сканирования шаблонов.
14///
15/// Для каждого использованного тега, атрибута и директивы ищет компонент в
16/// реестре; результат отсортирован и без дубликатов.
17pub fn needed_components(
18    scan: &ScanResult,
19    registry: &ComponentRegistry,
20) -> Vec<RequiredComponent> {
21    let mut names: BTreeSet<String> = BTreeSet::new();
22    for tag in scan.tags.keys() {
23        if let Some(component) = registry.component_for_element(tag) {
24            names.insert(component.name.clone());
25        }
26    }
27    for attr in scan.attributes.keys() {
28        if let Some(component) = registry.component_for_attribute(attr) {
29            names.insert(component.name.clone());
30            continue;
31        }
32        if is_directive(attr)
33            && let Some(component) = registry.component_for_directive(attr)
34        {
35            names.insert(component.name.clone());
36        }
37    }
38    names
39        .into_iter()
40        .map(|name| RequiredComponent { name })
41        .collect()
42}
43
44/// Атрибут похож на директиву (`@click`, `v-model`, `:disabled`).
45fn is_directive(attr: &str) -> bool {
46    attr.starts_with('@') || attr.starts_with(':') || attr.starts_with("v-")
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::scan::ScanResult;
53
54    fn scan_with(tags: &[&str], attrs: &[&str]) -> ScanResult {
55        let mut scan = ScanResult::default();
56        for t in tags {
57            scan.tags.insert((*t).to_string(), 1);
58        }
59        for a in attrs {
60            scan.attributes.insert((*a).to_string(), 1);
61        }
62        scan
63    }
64
65    #[test]
66    fn resolves_needed_components_from_tags() {
67        let registry = ComponentRegistry::builtin();
68        let scan = scan_with(&["text", "checkbox", "div"], &[]);
69        let needed = needed_components(&scan, &registry);
70        let names: Vec<_> = needed.iter().map(|c| c.name.as_str()).collect();
71        assert_eq!(
72            names,
73            vec!["light-fw-element-form"],
74            "text и div входят в ядро и не сопоставляются с компонентами"
75        );
76    }
77
78    #[test]
79    fn resolves_directives_and_attributes() {
80        let registry = ComponentRegistry::builtin();
81        let scan = scan_with(&[], &["required", "@validate"]);
82        let needed = needed_components(&scan, &registry);
83        assert_eq!(needed.len(), 1);
84        assert_eq!(needed[0].name, "light-fw-behavior-validation");
85    }
86
87    #[test]
88    fn no_duplicates_for_shared_component() {
89        let registry = ComponentRegistry::builtin();
90        let scan = scan_with(&["checkbox", "radio"], &["required"]);
91        let needed = needed_components(&scan, &registry);
92        assert_eq!(needed.len(), 2, "form + validation, без дублей");
93    }
94}