rslint_cli/
files.rs

1//! The structure responsible for managing IO and the files implementation for codespan.
2
3use crate::lint_warn;
4use ignore::{WalkBuilder, WalkState};
5use rslint_core::File;
6use rslint_errors::file::{FileId, Files};
7use std::collections::HashMap;
8use std::fs::read_to_string;
9use std::ops::Range;
10use std::path::PathBuf;
11
12/// A list of the extension of files linted
13const LINTED_FILES: [&str; 3] = ["js", "mjs", "ts"];
14
15/// The filename of the ignore file for RSLint
16const RSLINT_IGNORE_FILE: &str = ".rslintignore";
17
18/// The structure for managing IO to and from the core runner.
19/// The walker uses multithreaded IO, spawning a thread for every file being loaded.
20// TODO: use IO_Uring for linux
21#[derive(Debug, Clone, PartialEq, Eq, Default)]
22pub struct FileWalker {
23    pub files: HashMap<usize, File>,
24}
25
26impl Files for FileWalker {
27    fn name(&self, id: FileId) -> Option<&str> {
28        let entry = self.files.get(&id)?;
29        let name = entry
30            .path
31            .as_ref()
32            .and_then(|path| path.to_str())
33            .unwrap_or_else(|| entry.name.as_str());
34        Some(name)
35    }
36
37    fn source(&self, id: FileId) -> Option<&str> {
38        let entry = self.files.get(&id)?;
39        Some(&entry.source)
40    }
41
42    fn line_index(&self, id: FileId, byte_index: usize) -> Option<usize> {
43        Some(self.files.get(&id)?.line_index(byte_index))
44    }
45
46    fn line_range(&self, file_id: FileId, line_index: usize) -> Option<Range<usize>> {
47        self.files.get(&file_id)?.line_range(line_index)
48    }
49}
50
51impl FileWalker {
52    pub fn empty() -> Self {
53        Self {
54            files: HashMap::new(),
55        }
56    }
57
58    /// Make a new file walker from a compiled glob pattern. This also
59    /// skips any unreadable files/dirs
60    pub fn from_glob_parallel(paths: Vec<PathBuf>, num_threads: usize) -> Self {
61        let mut base = Self::default();
62        base.load_files_parallel(paths.into_iter(), num_threads, false, None, false);
63        base
64    }
65
66    pub fn load_files_parallel(
67        &mut self,
68        paths: impl Iterator<Item = PathBuf>,
69        num_threads: usize,
70        no_ignore: bool,
71        ignore_file: Option<PathBuf>,
72        use_gitignore: bool,
73    ) {
74        let build_walker = |path: &PathBuf| {
75            let mut builder = WalkBuilder::new(path);
76            builder.standard_filters(false);
77
78            if !no_ignore {
79                match ignore_file.as_ref() {
80                    Some(file) => {
81                        if let Some(err) = builder.add_ignore(file) {
82                            crate::lint_warn!("invalid gitignore file: {}", err);
83                        }
84                    }
85                    None => {
86                        builder.add_custom_ignore_filename(RSLINT_IGNORE_FILE);
87                    }
88                }
89
90                builder
91                    .parents(true)
92                    .hidden(true)
93                    .git_global(use_gitignore)
94                    .git_ignore(use_gitignore)
95                    .git_exclude(use_gitignore);
96            }
97
98            builder.threads(num_threads).build_parallel()
99        };
100
101        for path in paths {
102            let (tx, rx) = std::sync::mpsc::channel();
103            build_walker(&path).run(|| {
104                let tx = tx.clone();
105                Box::new(move |entry| {
106                    let path = match entry {
107                        Ok(entry) => match entry.file_type() {
108                            Some(typ) if !typ.is_dir() => entry.into_path(),
109                            _ => return WalkState::Continue,
110                        },
111                        Err(err) => {
112                            crate::lint_warn!("invalid gitignore file: {}", err);
113                            return WalkState::Continue;
114                        }
115                    };
116
117                    // check if this is a js/ts file
118                    let ext = path.extension().unwrap_or_default().to_string_lossy();
119                    if !LINTED_FILES.contains(&ext.as_ref()) {
120                        return WalkState::Continue;
121                    }
122
123                    // read the content of the file
124                    let content = match std::fs::read_to_string(&path) {
125                        Ok(c) => c,
126                        Err(err) => {
127                            crate::lint_err!("failed to read file {}: {}", path.display(), err);
128                            return WalkState::Continue;
129                        }
130                    };
131
132                    tx.send(File::new_concrete(content, path))
133                        .expect("failed to send files to receiver thread");
134                    WalkState::Continue
135                })
136            });
137
138            drop(tx);
139
140            self.files
141                .extend(rx.into_iter().map(|file| (file.id, file)));
142        }
143    }
144
145    pub fn line_start(&self, id: usize, line_index: usize) -> Option<usize> {
146        self.files.get(&id)?.line_start(line_index)
147    }
148
149    /// try loading a file's source code and updating the correspoding file in the walker
150    pub fn maybe_update_file_src(&mut self, path: PathBuf) {
151        if let Some(file) = self.files.values_mut().find(|f| {
152            f.path
153                .clone()
154                .map_or(false, |x| x.file_name() == path.file_name())
155        }) {
156            let src = if let Ok(src) = read_to_string(&path) {
157                src
158            } else {
159                return lint_warn!(
160                    "failed to reload the source code at `{}`",
161                    path.to_string_lossy()
162                );
163            };
164            file.source = src;
165            file.line_starts = File::line_starts(&file.source).collect();
166        }
167    }
168}