From 2098b27a200152f949bf08946c639d632146fb9b Mon Sep 17 00:00:00 2001
From: xFrednet <xFrednet@gmail.com>
Date: Sun, 27 Jun 2021 16:59:17 +0200
Subject: [PATCH] Added `cargo dev setup vscode-tasks` for simplicity

---
 clippy_dev/src/main.rs           |  14 +++++
 clippy_dev/src/setup/git_hook.rs |   6 ++
 clippy_dev/src/setup/mod.rs      |  21 +++++++
 clippy_dev/src/setup/vscode.rs   | 104 +++++++++++++++++++++++++++++++
 util/etc/vscode-tasks.json       |  57 +++++++++++++++++
 5 files changed, 202 insertions(+)
 create mode 100644 clippy_dev/src/setup/vscode.rs
 create mode 100644 util/etc/vscode-tasks.json

diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs
index f5bd08657ea..17c321d6c79 100644
--- a/clippy_dev/src/main.rs
+++ b/clippy_dev/src/main.rs
@@ -43,11 +43,13 @@ fn main() {
                     .expect("this field is mandatory and therefore always valid"),
             ),
             ("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")),
+            ("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")),
             _ => {},
         },
         ("remove", Some(sub_command)) => match sub_command.subcommand() {
             ("git-hook", Some(_)) => setup::git_hook::remove_hook(),
             ("intellij", Some(_)) => setup::intellij::remove_rustc_src(),
+            ("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(),
             _ => {},
         },
         ("serve", Some(matches)) => {
@@ -180,6 +182,17 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
                                 .help("Forces the override of an existing git pre-commit hook")
                                 .required(false),
                         ),
+                )
+                .subcommand(
+                    SubCommand::with_name("vscode-tasks")
+                        .about("Add several tasks to vscode for formatting, validation and testing")
+                        .arg(
+                            Arg::with_name("force-override")
+                                .long("force-override")
+                                .short("f")
+                                .help("Forces the override of existing vs code tasks")
+                                .required(false),
+                        ),
                 ),
         )
         .subcommand(
@@ -187,6 +200,7 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
                 .about("Support for undoing changes done by the setup command")
                 .setting(AppSettings::ArgRequiredElseHelp)
                 .subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook"))
+                .subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks"))
                 .subcommand(
                     SubCommand::with_name("intellij")
                         .about("Removes rustc source paths added via `cargo dev setup intellij`"),
diff --git a/clippy_dev/src/setup/git_hook.rs b/clippy_dev/src/setup/git_hook.rs
index f27b69a195b..3fbb77d5923 100644
--- a/clippy_dev/src/setup/git_hook.rs
+++ b/clippy_dev/src/setup/git_hook.rs
@@ -1,6 +1,8 @@
 use std::fs;
 use std::path::Path;
 
+use super::verify_inside_clippy_dir;
+
 /// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
 /// I've decided against this for the sake of simplicity and to make sure that it doesn't install
 /// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
@@ -36,6 +38,10 @@ pub fn install_hook(force_override: bool) {
 }
 
 fn check_precondition(force_override: bool) -> bool {
+    if !verify_inside_clippy_dir() {
+        return false;
+    }
+
     // Make sure that we can find the git repository
     let git_path = Path::new(REPO_GIT_DIR);
     if !git_path.exists() || !git_path.is_dir() {
diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs
index 3834f5a1842..a1e4dd103b8 100644
--- a/clippy_dev/src/setup/mod.rs
+++ b/clippy_dev/src/setup/mod.rs
@@ -1,2 +1,23 @@
 pub mod git_hook;
 pub mod intellij;
+pub mod vscode;
+
+use std::path::Path;
+
+const CLIPPY_DEV_DIR: &str = "clippy_dev";
+
+/// This function verifies that the tool is being executed in the clippy directory.
+/// This is useful to ensure that setups only modify Clippys resources. The verification
+/// is done by checking that `clippy_dev` is a sub directory of the current directory.
+///
+/// It will print an error message and return `false` if the directory could not be
+/// verified.
+fn verify_inside_clippy_dir() -> bool {
+    let path = Path::new(CLIPPY_DEV_DIR);
+    if path.exists() && path.is_dir() {
+        true
+    } else {
+        eprintln!("error: unable to verify that the working directory is clippys directory");
+        false
+    }
+}
diff --git a/clippy_dev/src/setup/vscode.rs b/clippy_dev/src/setup/vscode.rs
new file mode 100644
index 00000000000..d59001b2c66
--- /dev/null
+++ b/clippy_dev/src/setup/vscode.rs
@@ -0,0 +1,104 @@
+use std::fs;
+use std::path::Path;
+
+use super::verify_inside_clippy_dir;
+
+const VSCODE_DIR: &str = ".vscode";
+const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
+const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
+
+pub fn install_tasks(force_override: bool) {
+    if !check_install_precondition(force_override) {
+        return;
+    }
+
+    match fs::copy(TASK_SOURCE_FILE, TASK_TARGET_FILE) {
+        Ok(_) => {
+            println!("info: the task file can be removed with `cargo dev remove vscode-tasks`");
+            println!("vscode tasks successfully installed");
+        },
+        Err(err) => eprintln!(
+            "error: unable to copy `{}` to `{}` ({})",
+            TASK_SOURCE_FILE, TASK_TARGET_FILE, err
+        ),
+    }
+}
+
+fn check_install_precondition(force_override: bool) -> bool {
+    if !verify_inside_clippy_dir() {
+        return false;
+    }
+
+    let vs_dir_path = Path::new(VSCODE_DIR);
+    if vs_dir_path.exists() {
+        // verify the target will be valid
+        if !vs_dir_path.is_dir() {
+            eprintln!("error: the `.vscode` path exists but seems to be a file");
+            return false;
+        }
+
+        // make sure that we don't override any existing tasks by accident
+        let path = Path::new(TASK_TARGET_FILE);
+        if path.exists() {
+            if force_override {
+                return delete_vs_task_file(path);
+            }
+
+            eprintln!(
+                "error: there is already a `task.json` file inside the `{}` directory",
+                VSCODE_DIR
+            );
+            println!("info: use the `--force-override` flag to override the existing `task.json` file");
+            return false;
+        }
+    } else {
+        match fs::create_dir(vs_dir_path) {
+            Ok(_) => {
+                println!("info: created `{}` directory for clippy", VSCODE_DIR);
+            },
+            Err(err) => {
+                eprintln!(
+                    "error: the task target directory `{}` could not be created ({})",
+                    VSCODE_DIR, err
+                );
+            },
+        }
+    }
+
+    true
+}
+
+pub fn remove_tasks() {
+    let path = Path::new(TASK_TARGET_FILE);
+    if path.exists() {
+        if delete_vs_task_file(path) {
+            try_delete_vs_directory_if_empty();
+            println!("vscode tasks successfully removed");
+        }
+    } else {
+        println!("no vscode tasks were found");
+    }
+}
+
+fn delete_vs_task_file(path: &Path) -> bool {
+    if let Err(err) = fs::remove_file(path) {
+        eprintln!("error: unable to delete the existing `tasks.json` file ({})", err);
+        return false;
+    }
+
+    true
+}
+
+/// This function will try to delete the `.vscode` directory if it's empty.
+/// It may fail silently.
+fn try_delete_vs_directory_if_empty() {
+    let path = Path::new(VSCODE_DIR);
+    if path.read_dir().map_or(false, |mut iter| iter.next().is_none()) {
+        // The directory is empty. We just try to delete it but allow a silence
+        // fail as an empty `.vscode` directory is still valid
+        let _silence_result = fs::remove_dir(path);
+    } else {
+        // The directory is not empty or could not be read. Either way don't take
+        // any further actions
+    }
+}
diff --git a/util/etc/vscode-tasks.json b/util/etc/vscode-tasks.json
new file mode 100644
index 00000000000..aee31b5aa27
--- /dev/null
+++ b/util/etc/vscode-tasks.json
@@ -0,0 +1,57 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "cargo check",
+            "type": "shell",
+            "command": "cargo check",
+            "problemMatcher": [],
+            "group": {
+                "kind": "build",
+                "isDefault": true,
+            },
+        },
+        {
+            "label": "cargo fmt",
+            "type": "shell",
+            "command": "cargo dev fmt",
+            "problemMatcher": [],
+            "group": "none",
+        },
+        {
+            "label": "cargo uitest",
+            "type": "shell",
+            "command": "cargo uitest",
+            "options": {
+                "env": {
+                    "RUST_BACKTRACE": "1",
+                    // This task will usually execute all UI tests inside `tests/ui` you can
+                    // optionally uncomment the line below and only run a specific test.
+                    //
+                    // See: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md#testing
+                    //
+                    // "TESTNAME": "<TODO>",
+                },
+            },
+            "problemMatcher": [],
+            "group": {
+                "kind": "test",
+                "isDefault": true,
+            }
+        },
+        {
+            "label": "cargo test",
+            "type": "shell",
+            "command": "cargo test",
+            "problemMatcher": [],
+            "group": "test",
+        },
+        {
+            "label": "cargo dev bless",
+            "type": "shell",
+            "command": "cargo dev bless",
+            "problemMatcher": [],
+            "group": "none",
+        },
+    ],
+}