1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use std::convert::Infallible;

use codespan_reporting::diagnostic::Severity;
use itertools::Itertools;

use kodept_ast::{FileDeclaration, ModuleDeclaration, ModuleKind};
use kodept_ast::visitor::TraversingResult;
use kodept_ast::visitor::visit_side::{RefVisitGuard, VisitSide};
use kodept_core::impl_named;
use kodept_core::structure::Located;
use kodept_core::structure::rlt::Module;

use crate::analyzer::Analyzer;
use crate::error::report::ReportMessage;
use crate::traits::Context;
use crate::warn_about_broken_rlt;

#[derive(Debug)]
pub struct ModuleUniquenessAnalyzer;
#[derive(Debug)]
pub struct GlobalModuleAnalyzer;

impl_named!(ModuleUniquenessAnalyzer);
impl_named!(GlobalModuleAnalyzer);

pub struct DuplicatedModules(String);
pub struct NonGlobalModule(String);

impl From<DuplicatedModules> for ReportMessage {
    fn from(value: DuplicatedModules) -> Self {
        ReportMessage::new(
            Severity::Error,
            "SE001",
            format!("File contains duplicated module `{}`", value.0),
        )
    }
}

impl From<NonGlobalModule> for ReportMessage {
    fn from(value: NonGlobalModule) -> Self {
        ReportMessage::new(
            Severity::Warning,
            "SE002",
            format!(
                "Consider replace brackets in module statement `{}` to `=>` operator",
                value.0
            ),
        )
    }
}

impl Analyzer for GlobalModuleAnalyzer {
    type Error = Infallible;
    type Node<'n> = &'n FileDeclaration;

    fn analyze<'n, 'c, C: Context<'c>>(
        &self,
        guard: RefVisitGuard<Self::Node<'n>>,
        context: &mut C,
    ) -> TraversingResult<Self::Error> {
        let (node, token) = guard.allow_only(VisitSide::Entering)?;
        if let [m @ ModuleDeclaration {
            kind: ModuleKind::Ordinary,
            name,
            ..
        }] = node.modules(&context.tree(), &*token).as_slice()
        {
            match context.access(*m) {
                Some(Module::Global { .. }) => {}
                Some(Module::Ordinary { lbrace, rbrace, .. }) => context.add_report(
                    vec![lbrace.location(), rbrace.location()],
                    NonGlobalModule(name.clone()),
                ),
                None => {
                    warn_about_broken_rlt::<Module>();
                    context.add_report(vec![], NonGlobalModule(name.clone()))
                }
            };
            Ok(())
        } else {
            Ok(())
        }
    }
}

impl Analyzer for ModuleUniquenessAnalyzer {
    type Error = Infallible;
    type Node<'n> = &'n FileDeclaration;

    fn analyze<'n, 'c, C: Context<'c>>(
        &self,
        guard: RefVisitGuard<Self::Node<'n>>,
        context: &mut C,
    ) -> TraversingResult<Self::Error> {
        let tree = context.tree();
        let (node, token) = guard.allow_only(VisitSide::Entering)?;
        let group = node
            .modules(&tree, &*token)
            .into_iter()
            .group_by(|it| &it.name);
        let non_unique = group
            .into_iter()
            .map(|it| (it.0, it.1.collect_vec()))
            .filter(|(_, group)| group.len() > 1)
            .map(|(name, group)| {
                (
                    name.clone(),
                    group
                        .into_iter()
                        .filter_map(|it| context.access(it))
                        .map(|it: &Module| it.get_keyword().location())
                        .collect_vec(),
                )
            })
            .collect_vec();

        for (name, positions) in non_unique {
            context.add_report(positions, DuplicatedModules(name))
        }

        Ok(())
    }
}