@@ -6,3 +6,25 @@
Loading
6 6
    absolute_path.push(file_path);
7 7
    Ok(absolute_path.to_string_lossy().to_string())
8 8
}
9 +
10 +
pub fn get_relative_file_path(file_path: impl AsRef<Path>) -> Result<String, Error> {
11 +
    let current_dir = std::env::current_dir()?;
12 +
    let relative_path = file_path
13 +
        .as_ref()
14 +
        .strip_prefix(current_dir.to_string_lossy().to_string())?;
15 +
    Ok(relative_path.to_string_lossy().to_string())
16 +
}
17 +
18 +
#[cfg(test)]
19 +
mod tests {
20 +
    use super::*;
21 +
    #[test]
22 +
    fn test_file_path_conversion() -> Result<(), crate::error::Error> {
23 +
        let relative_path = "foo/bar.rs";
24 +
        assert_eq!(
25 +
            relative_path,
26 +
            get_relative_file_path(get_absolute_file_path(relative_path)?)?
27 +
        );
28 +
        Ok(())
29 +
    }
30 +
}

@@ -1,7 +1,9 @@
Loading
1 1
use crate::error::Error;
2 +
use crate::healer::Healer;
2 3
use crate::linter::{Lint, Linter, Location};
3 4
use crate::utils::get_absolute_file_path;
4 -
use serde::Deserialize;
5 +
use crate::utils::get_relative_file_path;
6 +
use serde::{Deserialize, Serialize};
5 7
use serde_json;
6 8
use std::path::{Path, PathBuf};
7 9
use std::process::Command;
@@ -9,6 +11,12 @@
Loading
9 11
#[derive(Default)]
10 12
pub struct RustFmt {}
11 13
14 +
#[derive(Serialize)]
15 +
struct FmtLocation {
16 +
    file: String,
17 +
    range: [u32; 2],
18 +
}
19 +
12 20
impl Linter for RustFmt {
13 21
    fn lints(&self, working_dir: impl Into<PathBuf>) -> Result<Vec<Lint>, Error> {
14 22
        let working_dir = working_dir.into();
@@ -21,6 +29,53 @@
Loading
21 29
    }
22 30
}
23 31
32 +
impl Healer for RustFmt {
33 +
    // Skipped from code coverage
34 +
    // because an external command
35 +
    // cannot be easily unit tested
36 +
    #[cfg_attr(tarpaulin, skip)]
37 +
    fn heal(&self, lints: Vec<Lint>) -> Result<(), crate::error::Error> {
38 +
        let l = &lints_as_json(&lints)?;
39 +
        let fmt_fix = Command::new("cargo")
40 +
            .args(&[
41 +
                "+nightly",
42 +
                "fmt",
43 +
                "--",
44 +
                "--unstable-features",
45 +
                "--file-lines",
46 +
                l,
47 +
                "--skip-children",
48 +
            ])
49 +
            .output()
50 +
            .expect("failed to run rustfmt");
51 +
52 +
        if fmt_fix.status.success() {
53 +
            Ok(())
54 +
        } else {
55 +
            Err(crate::error::Error::Command(String::from_utf8(
56 +
                fmt_fix.stderr,
57 +
            )?))
58 +
        }
59 +
    }
60 +
}
61 +
62 +
fn lints_as_json(lints: &[Lint]) -> Result<String, crate::error::Error> {
63 +
    let locations: Vec<FmtLocation> = lints
64 +
        .iter()
65 +
        .filter_map(|l| {
66 +
            if let Ok(file) = get_relative_file_path(l.location.path.clone()) {
67 +
                Some(FmtLocation {
68 +
                    file,
69 +
                    range: l.location.lines,
70 +
                })
71 +
            } else {
72 +
                None
73 +
            }
74 +
        })
75 +
        .collect();
76 +
    Ok(serde_json::to_string(&locations)?)
77 +
}
78 +
24 79
impl RustFmt {
25 80
    fn command_parameters() -> Vec<&'static str> {
26 81
        vec!["+nightly", "fmt", "--", "--emit", "json"]
@@ -185,4 +240,44 @@
Loading
185 240
186 241
        Ok(())
187 242
    }
243 +
244 +
    #[test]
245 +
    fn test_lints_as_json() -> Result<(), crate::error::Error> {
246 +
        let expected_output = r#"[{"file":"src/lib.rs","range":[1,2]},{"file":"foo_bar.rs","range":[11,19]},{"file":"baz.rs","range":[1,1]}]"#;
247 +
248 +
        let lints_to_transform = vec![
249 +
            Lint {
250 +
                message: String::new(),
251 +
                location: Location {
252 +
                    path: get_absolute_file_path("src/lib.rs")?,
253 +
                    lines: [1, 2],
254 +
                },
255 +
            },
256 +
            Lint {
257 +
                message: String::new(),
258 +
                location: Location {
259 +
                    path: get_absolute_file_path("foo_bar.rs")?,
260 +
                    lines: [11, 19],
261 +
                },
262 +
            },
263 +
            Lint {
264 +
                message: String::new(),
265 +
                location: Location {
266 +
                    path: get_absolute_file_path("baz.rs")?,
267 +
                    lines: [1, 1],
268 +
                },
269 +
            },
270 +
            Lint {
271 +
                message: "this one won't parse because the path is not absolute".to_string(),
272 +
                location: Location {
273 +
                    path: "wont_pass.rs".to_string(),
274 +
                    lines: [42, 42],
275 +
                },
276 +
            },
277 +
        ];
278 +
279 +
        let actual_output = lints_as_json(&lints_to_transform)?;
280 +
        assert_eq!(expected_output, actual_output);
281 +
        Ok(())
282 +
    }
188 283
}

@@ -1,9 +1,10 @@
Loading
1 1
use crate::config::*;
2 +
use crate::healer::Healer;
2 3
use crate::linter::{Lint, Linter};
3 4
use crate::vcs::*;
4 5
use std::path::PathBuf;
5 6
6 -
pub struct Scout<V, C, L>
7 +
pub struct Scout<'s, V, C, L>
7 8
where
8 9
    V: VCS,
9 10
    C: Config,
@@ -11,16 +12,16 @@
Loading
11 12
{
12 13
    vcs: V,
13 14
    config: C,
14 -
    linter: L,
15 +
    linter: &'s L,
15 16
}
16 17
17 -
impl<V, C, L> Scout<V, C, L>
18 +
impl<'s, V, C, L> Scout<'s, V, C, L>
18 19
where
19 20
    V: VCS,
20 21
    C: Config,
21 22
    L: Linter,
22 23
{
23 -
    pub fn new(vcs: V, config: C, linter: L) -> Self {
24 +
    pub fn new(vcs: V, config: C, linter: &'s L) -> Self {
24 25
        Self {
25 26
            vcs,
26 27
            config,
@@ -47,6 +48,26 @@
Loading
47 48
    }
48 49
}
49 50
51 +
pub struct Fixer<H>
52 +
where
53 +
    H: Healer,
54 +
{
55 +
    medic: H,
56 +
}
57 +
58 +
impl<H> Fixer<H>
59 +
where
60 +
    H: Healer,
61 +
{
62 +
    pub fn new(medic: H) -> Self {
63 +
        Self { medic }
64 +
    }
65 +
    pub fn run(&self, lints: Vec<Lint>) -> Result<(), crate::error::Error> {
66 +
        println!("[Scout] - applying fixes");
67 +
        self.medic.heal(lints)
68 +
    }
69 +
}
70 +
50 71
fn diff_in_member(member: &PathBuf, sections: &[Section]) -> bool {
51 72
    if let Some(m) = member.to_str() {
52 73
        for s in sections {
@@ -94,7 +115,6 @@
Loading
94 115
    use std::cell::RefCell;
95 116
    use std::clone::Clone;
96 117
    use std::path::{Path, PathBuf};
97 -
    use std::rc::Rc;
98 118
    struct TestVCS {
99 119
        sections: Vec<Section>,
100 120
        sections_called: RefCell<bool>,
@@ -117,20 +137,20 @@
Loading
117 137
        // Using a RefCell here because lints
118 138
        // takes &self and not &mut self.
119 139
        // We use usize here because we will compare it to a Vec::len()
120 -
        lints_times_called: Rc<RefCell<usize>>,
140 +
        times_called: RefCell<usize>,
121 141
        lints: Vec<Lint>,
122 142
    }
123 143
    impl TestLinter {
124 144
        pub fn new() -> Self {
125 145
            Self {
126 -
                lints_times_called: Rc::new(RefCell::new(0)),
146 +
                times_called: RefCell::new(0),
127 147
                lints: Vec::new(),
128 148
            }
129 149
        }
130 150
131 151
        pub fn with_lints(lints: Vec<Lint>) -> Self {
132 152
            Self {
133 -
                lints_times_called: Rc::new(RefCell::new(0)),
153 +
                times_called: RefCell::new(0),
134 154
                lints,
135 155
            }
136 156
        }
@@ -140,10 +160,16 @@
Loading
140 160
            &self,
141 161
            _working_dir: impl Into<PathBuf>,
142 162
        ) -> Result<Vec<Lint>, crate::error::Error> {
143 -
            *self.lints_times_called.borrow_mut() += 1;
163 +
            *self.times_called.borrow_mut() += 1;
144 164
            Ok(self.lints.clone())
145 165
        }
146 166
    }
167 +
    impl Healer for TestLinter {
168 +
        fn heal(&self, _lints: Vec<Lint>) -> Result<(), crate::error::Error> {
169 +
            *self.times_called.borrow_mut() += 1;
170 +
            Ok(())
171 +
        }
172 +
    }
147 173
    struct TestConfig {
148 174
        members: Vec<String>,
149 175
    }
@@ -165,13 +191,12 @@
Loading
165 191
        // No members so we won't have to iterate
166 192
        let config = TestConfig::new(Vec::new());
167 193
        let expected_times_called = 0;
168 -
        let actual_times_called = Rc::clone(&linter.lints_times_called);
169 -
        let scout = Scout::new(vcs, config, linter);
194 +
        let scout = Scout::new(vcs, config, &linter);
170 195
        // We don't check for the lints result here.
171 196
        // It is already tested in the linter tests
172 197
        // and in intersection tests
173 198
        let _ = scout.run()?;
174 -
        assert_eq!(expected_times_called, *actual_times_called.borrow());
199 +
        assert_eq!(expected_times_called, *linter.times_called.borrow());
175 200
        Ok(())
176 201
    }
177 202
@@ -213,13 +238,12 @@
Loading
213 238
        // The member matches the file name
214 239
        let config = TestConfig::new(vec!["foo".to_string()]);
215 240
        let expected_times_called = 1;
216 -
        let actual_times_called = Rc::clone(&linter.lints_times_called);
217 -
        let scout = Scout::new(vcs, config, linter);
241 +
        let scout = Scout::new(vcs, config, &linter);
218 242
        // We don't check for the lints result here.
219 243
        // It is already tested in the linter tests
220 244
        // and in intersection tests
221 245
        let actual_lints_from_diff = scout.run()?;
222 -
        assert_eq!(expected_times_called, *actual_times_called.borrow());
246 +
        assert_eq!(expected_times_called, *linter.times_called.borrow());
223 247
        assert_eq!(expected_lints_from_diff, actual_lints_from_diff);
224 248
        Ok(())
225 249
    }
@@ -236,13 +260,12 @@
Loading
236 260
        // The member does not match the file name
237 261
        let config = TestConfig::new(vec!["foo".to_string()]);
238 262
        let expected_times_called = 0;
239 -
        let actual_times_called = Rc::clone(&linter.lints_times_called);
240 -
        let scout = Scout::new(vcs, config, linter);
263 +
        let scout = Scout::new(vcs, config, &linter);
241 264
        // We don't check for the lints result here.
242 265
        // It is already tested in the linter tests
243 266
        // and in intersection tests
244 267
        let _ = scout.run()?;
245 -
        assert_eq!(expected_times_called, *actual_times_called.borrow());
268 +
        assert_eq!(expected_times_called, *linter.times_called.borrow());
246 269
        Ok(())
247 270
    }
248 271
@@ -270,14 +293,27 @@
Loading
270 293
        ]);
271 294
        // We should run the linter on member1 and member2
272 295
        let expected_times_called = 2;
273 -
        let actual_times_called = Rc::clone(&linter.lints_times_called);
274 -
        let scout = Scout::new(vcs, config, linter);
296 +
        let scout = Scout::new(vcs, config, &linter);
275 297
        // We don't check for the lints result here.
276 298
        // It is already tested in the linter tests
277 299
        // and in intersection tests
278 300
        let _ = scout.run()?;
279 301
280 -
        assert_eq!(expected_times_called, *actual_times_called.borrow());
302 +
        assert_eq!(expected_times_called, *linter.times_called.borrow());
303 +
        Ok(())
304 +
    }
305 +
306 +
    #[test]
307 +
    fn test_heal() -> Result<(), crate::error::Error> {
308 +
        let fixer = TestLinter::new();
309 +
        let config = TestConfig::new(Vec::new());
310 +
        let vcs = TestVCS::new(Vec::new());
311 +
312 +
        let expected_times_called = 1;
313 +
        let scout = Scout::new(vcs, config, &fixer);
314 +
        let lints = scout.run()?;
315 +
        fixer.heal(lints)?;
316 +
        assert_eq!(expected_times_called, *fixer.times_called.borrow());
281 317
        Ok(())
282 318
    }
283 319
}

@@ -1,4 +1,5 @@
Loading
1 1
use cargo_scout_lib::config::rust::CargoConfig;
2 +
use cargo_scout_lib::healer::Healer;
2 3
use cargo_scout_lib::linter::clippy::Clippy;
3 4
use cargo_scout_lib::linter::rustfmt::RustFmt;
4 5
use cargo_scout_lib::linter::Lint;
@@ -15,9 +16,15 @@
Loading
15 16
)]
16 17
enum Command {
17 18
    Fmt(FmtOptions),
19 +
    Fix(FixCommand),
18 20
    Lint(LintOptions),
19 21
}
20 22
23 +
#[derive(StructOpt)]
24 +
enum FixCommand {
25 +
    Fmt(LintOptions),
26 +
}
27 +
21 28
#[derive(Debug, StructOpt)]
22 29
struct FmtOptions {
23 30
    #[structopt(
@@ -74,11 +81,28 @@
Loading
74 81
#[cfg_attr(tarpaulin, skip)]
75 82
fn main() -> Result<(), Error> {
76 83
    match Command::from_args() {
84 +
        Command::Fix(opts) => run_fix(opts),
77 85
        Command::Fmt(opts) => run_fmt(opts),
78 86
        Command::Lint(opts) => run_lint(opts),
79 87
    }
80 88
}
81 89
90 +
#[cfg_attr(tarpaulin, skip)]
91 +
#[allow(irrefutable_let_patterns)]
92 +
fn run_fix(fix_command: FixCommand) -> Result<(), Error> {
93 +
    if let FixCommand::Fmt(opts) = fix_command {
94 +
        let vcs = Git::with_target(opts.branch);
95 +
        let config = CargoConfig::from_manifest_path(opts.cargo_toml)?;
96 +
        let linter = RustFmt::default();
97 +
98 +
        let scout = Scout::new(vcs, config, &linter); // TODO: do not consume?
99 +
        let relevant_lints = scout.run()?;
100 +
        linter.heal(relevant_lints)
101 +
    } else {
102 +
        Err(Error::InvalidCommand)
103 +
    }
104 +
}
105 +
82 106
#[cfg_attr(tarpaulin, skip)]
83 107
fn run_lint(opts: LintOptions) -> Result<(), Error> {
84 108
    let fail_if_errors = opts.without_error;
@@ -92,7 +116,7 @@
Loading
92 116
        .set_all_features(opts.all_features)
93 117
        .set_features(opts.features)
94 118
        .set_preview(opts.preview);
95 -
    let scout = Scout::new(vcs, config, linter);
119 +
    let scout = Scout::new(vcs, config, &linter);
96 120
    let relevant_lints = scout.run()?;
97 121
    return_warnings(&relevant_lints, fail_if_errors)
98 122
}
@@ -105,7 +129,7 @@
Loading
105 129
    let config = CargoConfig::from_manifest_path(opts.cargo_toml)?;
106 130
    let linter = RustFmt::default();
107 131
108 -
    let scout = Scout::new(vcs, config, linter);
132 +
    let scout = Scout::new(vcs, config, &linter);
109 133
    let relevant_lints = scout.run()?;
110 134
    return_warnings(&relevant_lints, fail_if_errors)
111 135
}
Files Coverage
cargo-scout-lib/src 91.99%
cargo-scout/src/main.rs 96.30%
Project Totals (7 files) 92.17%
191.1
TRAVIS_OS_NAME=linux
stable=

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading