Skip to main content

blockwatch/
lib.rs

1use serde::Serialize;
2
3mod block_parser;
4pub mod blocks;
5pub mod diff_parser;
6pub mod flags;
7pub mod language_parsers;
8mod tag_parser;
9pub mod validators;
10
11#[derive(Serialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
12struct Position {
13    // 1-based line number.
14    line: usize,
15    // 1-based character (column) number.
16    character: usize,
17}
18
19impl Position {
20    pub fn new(line: usize, character: usize) -> Self {
21        Self { line, character }
22    }
23}
24
25#[cfg(test)]
26mod test_utils {
27    use crate::blocks::{FileBlocks, FileSystem, PathChecker, parse_blocks};
28    use crate::diff_parser::LineChange;
29    use crate::language_parsers;
30    use crate::validators::ValidationContext;
31    use std::collections::{HashMap, HashSet};
32    use std::ops::Range;
33    use std::path::{Path, PathBuf};
34    use std::sync::Arc;
35
36    /// Finds the byte range of the first occurrence of a substring within a string.
37    ///
38    /// # Arguments
39    /// * `input` - The string to search in
40    /// * `substr` - The substring to find
41    pub(crate) fn substr_range(input: &str, substr: &str) -> Range<usize> {
42        let pos = input.find(substr).unwrap();
43        pos..(pos + substr.len())
44    }
45
46    pub(crate) struct FakeFileSystem {
47        files: HashMap<String, String>,
48    }
49
50    impl FakeFileSystem {
51        pub(crate) fn new(files: HashMap<String, String>) -> Self {
52            Self { files }
53        }
54    }
55
56    impl FileSystem for FakeFileSystem {
57        fn read_to_string(&self, path: &Path) -> anyhow::Result<String> {
58            Ok(self
59                .files
60                .get(&path.display().to_string())
61                .unwrap_or_else(|| panic!("File {} not found", path.display()))
62                .clone())
63        }
64
65        fn walk(&self) -> impl Iterator<Item = anyhow::Result<PathBuf>> {
66            self.files.keys().map(|p| Ok(PathBuf::from(p)))
67        }
68    }
69
70    pub(crate) struct FakePathChecker {
71        ignored_paths: HashSet<String>,
72    }
73
74    impl FakePathChecker {
75        pub(crate) fn with_ignored_paths(ignored_paths: HashSet<String>) -> Self {
76            Self { ignored_paths }
77        }
78
79        pub(crate) fn allow_all() -> Self {
80            Self::with_ignored_paths(HashSet::new())
81        }
82    }
83
84    impl PathChecker for FakePathChecker {
85        fn should_allow(&self, _unused_path: &Path) -> bool {
86            true
87        }
88
89        fn should_ignore(&self, path: &Path) -> bool {
90            self.ignored_paths.contains(&path.display().to_string())
91        }
92    }
93
94    /// Creates a [`ValidationContext`] for the given `file_name` with `contents` with all lines modified.
95    pub(crate) fn validation_context(file_name: &str, contents: &str) -> Arc<ValidationContext> {
96        let line_changes: Vec<LineChange> = contents
97            .lines()
98            .enumerate()
99            .map(|(line, _)| LineChange {
100                line: line + 1,
101                ranges: None,
102            })
103            .collect();
104        validation_context_with_changes(file_name, contents, line_changes)
105    }
106
107    /// Creates a [`ValidationContext`] for the given `file_name` with `contents` and specified `line_changes`.
108    pub(crate) fn validation_context_with_changes(
109        file_name: &str,
110        contents: &str,
111        line_changes: Vec<LineChange>,
112    ) -> Arc<ValidationContext> {
113        let file_system = FakeFileSystem::new(HashMap::from([(
114            file_name.to_string(),
115            contents.to_string(),
116        )]));
117        let line_changes_by_file = HashMap::from([(file_name.into(), line_changes)]);
118        Arc::new(ValidationContext::new(
119            parse_blocks(
120                line_changes_by_file,
121                false,
122                &file_system,
123                &FakePathChecker::allow_all(),
124                language_parsers::language_parsers().unwrap(),
125                HashMap::new(),
126            )
127            .unwrap(),
128        ))
129    }
130
131    pub(crate) fn merge_validation_contexts(
132        contexts: Vec<Arc<ValidationContext>>,
133    ) -> Arc<ValidationContext> {
134        let mut merged_modified_blocks = HashMap::new();
135        for context in contexts {
136            for (file_path, file_blocks) in &context.blocks {
137                merged_modified_blocks
138                    .entry(file_path.clone())
139                    .or_insert_with(|| FileBlocks {
140                        file_content: file_blocks.file_content.clone(),
141                        blocks_with_context: vec![],
142                    })
143                    .blocks_with_context
144                    .extend(file_blocks.blocks_with_context.clone());
145            }
146        }
147        Arc::new(ValidationContext::new(merged_modified_blocks))
148    }
149}