|
| 1 | +#[macro_use] extern crate duct; |
| 2 | +#[macro_use] extern crate pretty_assertions; |
| 3 | +extern crate tempdir; |
| 4 | +#[macro_use] extern crate log; |
| 5 | +extern crate env_logger; |
| 6 | +extern crate serde_json; |
| 7 | +extern crate rustfix; |
| 8 | + |
| 9 | +use std::fs; |
| 10 | +use std::error::Error; |
| 11 | +use std::path::{Path, PathBuf}; |
| 12 | +use std::collections::HashSet; |
| 13 | +use tempdir::TempDir; |
| 14 | + |
| 15 | +use rustfix::Replacement; |
| 16 | + |
| 17 | +fn compile_and_get_json_errors(file: &Path) -> Result<String, Box<Error>> { |
| 18 | + let tmp = TempDir::new("rustfix-tests")?; |
| 19 | + let better_call_clippy = cmd!( |
| 20 | + "clippy-driver", "rustc", file, |
| 21 | + "--error-format=json", "--emit=metadata", |
| 22 | + "--out-dir", tmp.path() |
| 23 | + ); |
| 24 | + let res = better_call_clippy |
| 25 | + .stdout_capture() |
| 26 | + .stderr_capture() |
| 27 | + .unchecked() |
| 28 | + .run()?; |
| 29 | + let stderr = String::from_utf8(res.stderr)?; |
| 30 | + |
| 31 | + use std::io::{Error, ErrorKind}; |
| 32 | + match res.status.code() { |
| 33 | + Some(0) | Some(1) => Ok(stderr), |
| 34 | + _ => Err(Box::new(Error::new( |
| 35 | + ErrorKind::Other, |
| 36 | + format!("failed with status {:?}: {}", res.status.code(), stderr), |
| 37 | + ))) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +fn read_file(path: &Path) -> Result<String, Box<Error>> { |
| 42 | + use std::io::Read; |
| 43 | + |
| 44 | + let mut buffer = String::new(); |
| 45 | + let mut file = fs::File::open(path)?; |
| 46 | + file.read_to_string(&mut buffer)?; |
| 47 | + Ok(buffer) |
| 48 | +} |
| 49 | + |
| 50 | +fn apply_suggestion(file_content: &mut String, suggestion: &Replacement) -> Result<String, Box<Error>> { |
| 51 | + use std::cmp::max; |
| 52 | + |
| 53 | + let mut new_content = String::new(); |
| 54 | + |
| 55 | + // Add the lines before the section we want to replace |
| 56 | + new_content.push_str(&file_content.lines() |
| 57 | + .take(max(suggestion.snippet.line_range.start.line - 1, 0) as usize) |
| 58 | + .collect::<Vec<_>>() |
| 59 | + .join("\n")); |
| 60 | + new_content.push_str("\n"); |
| 61 | + |
| 62 | + // Parts of line before replacement |
| 63 | + new_content.push_str(&file_content.lines() |
| 64 | + .nth(suggestion.snippet.line_range.start.line - 1) |
| 65 | + .unwrap_or("") |
| 66 | + .chars() |
| 67 | + .take(suggestion.snippet.line_range.start.column - 1) |
| 68 | + .collect::<String>()); |
| 69 | + |
| 70 | + // Insert new content! Finally! |
| 71 | + new_content.push_str(&suggestion.replacement); |
| 72 | + |
| 73 | + // Parts of line after replacement |
| 74 | + new_content.push_str(&file_content.lines() |
| 75 | + .nth(suggestion.snippet.line_range.end.line - 1) |
| 76 | + .unwrap_or("") |
| 77 | + .chars() |
| 78 | + .skip(suggestion.snippet.line_range.end.column - 1) |
| 79 | + .collect::<String>()); |
| 80 | + |
| 81 | + // Add the lines after the section we want to replace |
| 82 | + new_content.push_str("\n"); |
| 83 | + new_content.push_str(&file_content.lines() |
| 84 | + .skip(suggestion.snippet.line_range.end.line as usize) |
| 85 | + .collect::<Vec<_>>() |
| 86 | + .join("\n")); |
| 87 | + new_content.push_str("\n"); |
| 88 | + |
| 89 | + Ok(new_content) |
| 90 | +} |
| 91 | + |
| 92 | +fn test_rustfix_with_file<P: AsRef<Path>>(file: P) -> Result<(), Box<Error>> { |
| 93 | + let file: &Path = file.as_ref(); |
| 94 | + debug!("{:?}", file); |
| 95 | + let code = read_file(&file)?; |
| 96 | + let errors = compile_and_get_json_errors(file)?; |
| 97 | + |
| 98 | + if std::env::var("RUSTFIX_TEST_RECORD_JSON").is_ok() { |
| 99 | + use std::io::Write; |
| 100 | + let mut recorded_json = fs::File::create(&file.with_extension("recorded.json"))?; |
| 101 | + recorded_json.write_all(errors.as_bytes())?; |
| 102 | + } |
| 103 | + |
| 104 | + let expected_json = read_file(&file.with_extension("json"))?; |
| 105 | + |
| 106 | + assert_eq!( |
| 107 | + errors.trim(), |
| 108 | + expected_json.trim(), |
| 109 | + "got unexpected json from clippy" |
| 110 | + ); |
| 111 | + |
| 112 | + let suggestions = rustfix::get_suggestions_from_json(&errors, &HashSet::new()); |
| 113 | + let mut fixed = code.clone(); |
| 114 | + |
| 115 | + for sug in suggestions { |
| 116 | + trace!("{:?}", sug); |
| 117 | + for sol in sug.solutions { |
| 118 | + trace!("{:?}", sol); |
| 119 | + for r in sol.replacements { |
| 120 | + info!("replaced."); |
| 121 | + trace!("{:?}", r); |
| 122 | + fixed = apply_suggestion(&mut fixed, &r)?; |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + let expected_fixed = read_file(&file.with_extension("fixed.rs"))?; |
| 128 | + assert_eq!(fixed.trim(), expected_fixed.trim(), "file doesn't look fixed"); |
| 129 | + Ok(()) |
| 130 | +} |
| 131 | + |
| 132 | +fn get_fixture_files() -> Result<Vec<PathBuf>, Box<Error>> { |
| 133 | + Ok(fs::read_dir("./tests/fixtures")? |
| 134 | + .into_iter() |
| 135 | + .map(|e| e.unwrap().path()) |
| 136 | + .filter(|p| p.is_file()) |
| 137 | + .filter(|p| { |
| 138 | + let x = p.to_string_lossy(); |
| 139 | + x.ends_with(".rs") && !x.ends_with(".fixed.rs") |
| 140 | + }) |
| 141 | + .collect()) |
| 142 | +} |
| 143 | + |
| 144 | +#[test] |
| 145 | +fn fixtures() { |
| 146 | + let _ = env_logger::try_init(); |
| 147 | + |
| 148 | + for file in &get_fixture_files().unwrap() { |
| 149 | + test_rustfix_with_file(file).unwrap(); |
| 150 | + info!("passed: {:?}", file); |
| 151 | + } |
| 152 | +} |
0 commit comments