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
use osmium_libs_solidity_ast_extractor::{FunctionBody, Spanned};

use crate::linter::SolidFile;
use crate::rules::types::*;
use crate::types::*;

// global
pub const RULE_ID: &str = "function-max-lines";

// specific
const DEFAULT_SEVERITY: Severity = Severity::WARNING;
const DEFAULT_MAX_LINES: usize = 50;

pub struct FunctionMaxLines {
    number_max_lines: usize,
    data: RuleEntry,
}

impl RuleType for FunctionMaxLines {
    fn diagnose(&self, _file: &SolidFile, _files: &[SolidFile]) -> Vec<LintDiag> {
        let mut res = Vec::new();

        for contract in
            osmium_libs_solidity_ast_extractor::retriever::retrieve_contract_nodes(&_file.data)
        {
            for function in
                osmium_libs_solidity_ast_extractor::retriever::retrieve_functions_nodes(&contract)
            {
                let report = check_function_lines(&function, self.number_max_lines);
                if let Some(report) = report {
                    let start = report.start.line;
                    let end = report.end.line;

                    res.push(LintDiag {
                        id: RULE_ID.to_string(),
                        range: report,
                        severity: self.data.severity,
                        code: None,
                        source: None,
                        message: format!(
                            "Function body contains {} lines but allowed no more than {} lines",
                            end - start,
                            self.number_max_lines
                        ),
                        uri: _file.path.clone(),
                    });
                }
            }
        }
        res
    }
}

// returns a struct containing the line number of the start and end of the function if it is too long
fn check_function_lines(
    function: &osmium_libs_solidity_ast_extractor::ItemFunction,
    nb_max_line: usize,
) -> Option<Range> {
    if let FunctionBody::Block(block) = &function.body {
        let line_diff = block.span().end().line - block.span().start().line;
        let start_span = function.name.span().start();
        let end_span = block.span().end();
        if line_diff > nb_max_line {
            return Some(Range {
                start: Position {
                    line: start_span.line,
                    character: start_span.column,
                },
                end: Position {
                    line: end_span.line,
                    character: end_span.column,
                },
            });
        }
    }
    None
}

impl FunctionMaxLines {
    pub fn create(data: RuleEntry) -> Box<dyn RuleType> {
        let mut max_number_lines = DEFAULT_MAX_LINES;

        if let Some(data) = &data.data {
            let parsed: Result<usize, serde_json::Error> = serde_json::from_value(data.clone());
            match parsed {
                Ok(val) => max_number_lines = val,
                Err(_) => {
                    eprintln!("{} rule : bad config data", RULE_ID);
                }
            }
        } else {
            eprintln!("{} rule : bad config data", RULE_ID);
        }
        let rule = FunctionMaxLines {
            number_max_lines: max_number_lines,
            data,
        };
        Box::new(rule)
    }

    pub fn create_default() -> RuleEntry {
        RuleEntry {
            id: RULE_ID.to_string(),
            severity: DEFAULT_SEVERITY,
            data: Some(DEFAULT_MAX_LINES.into()),
        }
    }
}