diff --git a/.gitignore b/.gitignore index 4cf87f2f..de87c1e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ target/ **/*.rs.bk .DS_Store -*.pdb \ No newline at end of file +*.pdb +exercises/clippy/Cargo.toml +exercises/clippy/Cargo.lock diff --git a/exercises/clippy/README.md b/exercises/clippy/README.md new file mode 100644 index 00000000..60a12fe5 --- /dev/null +++ b/exercises/clippy/README.md @@ -0,0 +1,8 @@ +### Clippy + +The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code. + +If you used the installation script for Rustlings, Clippy should be already installed. +If not you can install it manually via `rustup component add clippy`. + +For more information about Clippy lints, please see [their documentation page](https://rust-lang.github.io/rust-clippy/master/). diff --git a/exercises/clippy/clippy1.rs b/exercises/clippy/clippy1.rs new file mode 100644 index 00000000..2b4c6354 --- /dev/null +++ b/exercises/clippy/clippy1.rs @@ -0,0 +1,15 @@ +// clippy1.rs +// The Clippy tool is a collection of lints to analyze your code +// so you can catch common mistakes and improve your Rust code. +// +// Execute `rustlings hint clippy1` for hints :) + +// I AM NOT DONE + +fn main() { + let x = 1.2331f64; + let y = 1.2332f64; + if y != x { + println!("Success!"); + } +} diff --git a/exercises/clippy/clippy2.rs b/exercises/clippy/clippy2.rs new file mode 100644 index 00000000..37af9ed0 --- /dev/null +++ b/exercises/clippy/clippy2.rs @@ -0,0 +1,13 @@ +// clippy2.rs +// Make me compile! Execute `rustlings hint clippy2` for hints :) + +// I AM NOT DONE + +fn main() { + let mut res = 42; + let option = Some(12); + for x in option { + res += x; + } + println!("{}", res); +} diff --git a/info.toml b/info.toml index b1c6db53..82c22b7a 100644 --- a/info.toml +++ b/info.toml @@ -529,6 +529,22 @@ hint = """ It should be doing some checking, returning an `Err` result if those checks fail, and only returning an `Ok` result if those checks determine that everything is... okay :)""" +# CLIPPY + +[[exercises]] +name = "clippy1" +path = "exercises/clippy/clippy1.rs" +mode = "clippy" +hint = """ +Floating point calculations are usually imprecise, so asking if two values are exactly equal is asking for trouble""" + +[[exercises]] +name = "clippy2" +path = "exercises/clippy/clippy2.rs" +mode = "clippy" +hint = """ +`for` loops over Option values are more clearly expressed as an `if let`""" + # STANDARD LIBRARY TYPES [[exercises]] diff --git a/install.ps1 b/install.ps1 index f6446100..04ea4a08 100644 --- a/install.ps1 +++ b/install.ps1 @@ -72,14 +72,19 @@ if (!($LASTEXITCODE -eq 0)) { # but anyone running pwsh 5 will have to pass the argument. $version = Invoke-WebRequest -UseBasicParsing https://api.github.com/repos/rust-lang/rustlings/releases/latest ` | ConvertFrom-Json | Select-Object -ExpandProperty tag_name -Write-Host "Checking out version $version..." -Set-Location $path -git checkout -q tags/$version Write-Host "Installing the 'rustlings' executable..." -cargo install --force --path . +cargo install --force --git https://github.com/rust-lang/rustlings --tag $version if (!(Get-Command rustlings -ErrorAction SilentlyContinue)) { Write-Host "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!" } +# Checking whether Clippy is installed. +# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514 +$clippy = (rustup component list | Select-String "clippy" | Select-String "installed") | Out-String +if (!$clippy) { + Write-Host "Installing the 'cargo-clippy' executable..." + rustup component add clippy +} + Write-Host "All done! Run 'rustlings' to get started." diff --git a/install.sh b/install.sh index 85bdad79..10750617 100755 --- a/install.sh +++ b/install.sh @@ -82,21 +82,24 @@ else echo "SUCCESS: Rust is up to date" fi -Path=${1:-rustlings/} -echo "Cloning Rustlings at $Path..." -git clone -q https://github.com/rust-lang/rustlings $Path - Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | python -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']);") -echo "Checking out version $Version..." -cd $Path -git checkout -q tags/$Version +CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin" echo "Installing the 'rustlings' executable..." -cargo install --force --path . +cargo install --force --git https://github.com/rust-lang/rustlings --tag $Version if ! [ -x "$(command -v rustlings)" ] then - echo "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!" + echo "WARNING: Please check that you have '$CargoBin' in your PATH environment variable!" +fi + +# Checking whether Clippy is installed. +# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514 +Clippy=$(rustup component list | grep "clippy" | grep "installed") +if [ -z "$Clippy" ] +then + echo "Installing the 'cargo-clippy' executable..." + rustup component add clippy fi echo "All done! Run 'rustlings' to get started." diff --git a/src/exercise.rs b/src/exercise.rs index d72eeb50..30b18643 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -1,7 +1,7 @@ use regex::Regex; use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -use std::fs::{remove_file, File}; +use std::fs::{self, remove_file, File}; use std::io::Read; use std::path::PathBuf; use std::process::{self, Command}; @@ -9,6 +9,7 @@ use std::process::{self, Command}; const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; const CONTEXT: usize = 2; +const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml"; fn temp_file() -> String { format!("./temp_{}", process::id()) @@ -19,6 +20,7 @@ fn temp_file() -> String { pub enum Mode { Compile, Test, + Clippy, } #[derive(Deserialize)] @@ -83,6 +85,34 @@ impl Exercise { .args(&["--test", self.path.to_str().unwrap(), "-o", &temp_file()]) .args(RUSTC_COLOR_ARGS) .output(), + Mode::Clippy => { + let cargo_toml = format!( + r#"[package] +name = "{}" +version = "0.0.1" +edition = "2018" +[[bin]] +name = "{}" +path = "{}.rs""#, + self.name, self.name, self.name + ); + fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml) + .expect("Failed to write 📎 Clippy 📎 Cargo.toml file."); + // Due to an issue with Clippy, a cargo clean is required to catch all lints. + // See https://github.com/rust-lang/rust-clippy/issues/2604 + // This is already fixed on master branch. See this issue to track merging into Cargo: + // https://github.com/rust-lang/rust-clippy/issues/3837 + Command::new("cargo") + .args(&["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) + .args(RUSTC_COLOR_ARGS) + .output() + .expect("Failed to run 'cargo clean'"); + Command::new("cargo") + .args(&["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) + .args(RUSTC_COLOR_ARGS) + .args(&["--", "-D", "warnings"]) + .output() + } } .expect("Failed to run 'compile' command."); diff --git a/src/run.rs b/src/run.rs index cfde7ab6..ebb0ae64 100644 --- a/src/run.rs +++ b/src/run.rs @@ -6,6 +6,7 @@ pub fn run(exercise: &Exercise) -> Result<(), ()> { match exercise.mode { Mode::Test => test(exercise)?, Mode::Compile => compile_and_run(exercise)?, + Mode::Clippy => compile_and_run(exercise)?, } Ok(()) } diff --git a/src/verify.rs b/src/verify.rs index 3d148960..229aa6d9 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -7,6 +7,7 @@ pub fn verify<'a>(start_at: impl IntoIterator) -> Result<() let compile_result = match exercise.mode { Mode::Test => compile_and_test(&exercise, RunMode::Interactive), Mode::Compile => compile_only(&exercise), + Mode::Clippy => compile_only(&exercise), }; if !compile_result.unwrap_or(false) { return Err(exercise); @@ -99,6 +100,7 @@ fn prompt_for_completion(exercise: &Exercise) -> bool { let success_msg = match exercise.mode { Mode::Compile => "The code is compiling!", Mode::Test => "The code is compiling, and the tests pass!", + Mode::Clippy => "The code is compiling, and 📎 Clippy 📎 is happy!", }; println!("");