From 72eb9de74739887874bebb08c71b574106ae80f1 Mon Sep 17 00:00:00 2001
From: gfreezy <gfreezy@gmail.com>
Date: Wed, 26 Dec 2018 00:45:13 +0800
Subject: [PATCH] add fix for removing unnecessary braces in use statements

---
 crates/ra_analysis/src/imp.rs        | 62 ++++++++++++----------------
 crates/ra_analysis/src/lib.rs        |  5 +--
 crates/ra_editor/src/code_actions.rs |  7 ++++
 crates/ra_editor/src/lib.rs          | 26 +++++++++---
 crates/ra_editor/src/typing.rs       | 31 ++++++--------
 5 files changed, 69 insertions(+), 62 deletions(-)

diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index a547c5a2055..69c9b104e7a 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -3,31 +3,32 @@ use std::{
     sync::Arc,
 };
 
-use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit, Severity};
+use rayon::prelude::*;
+use salsa::{Database, ParallelDatabase};
+
+use hir::{
+    self,
+    FnSignatureInfo,
+    Problem,
+    source_binder,
+};
+use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
+use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity};
 use ra_syntax::{
-    ast::{self, ArgListOwner, Expr, NameOwner, FnDef},
     algo::find_covering_node,
+    ast::{self, ArgListOwner, Expr, FnDef, NameOwner},
     AstNode, SourceFileNode,
     SyntaxKind::*,
     SyntaxNodeRef, TextRange, TextUnit,
 };
-use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
-use rayon::prelude::*;
-use salsa::{Database, ParallelDatabase};
-use hir::{
-    self,
-    source_binder,
-    FnSignatureInfo,
-    Problem,
-};
 
 use crate::{
-    completion::{completions, CompletionItem},
-    db,
-    symbol_index::{SymbolIndex, SymbolsDatabase, LibrarySymbolsQuery},
-    AnalysisChange, RootChange, Cancelable, CrateId, Diagnostic, FileId,
-    FileSystemEdit, FilePosition, Query, SourceChange, SourceFileEdit,
-    ReferenceResolution,
+    AnalysisChange,
+    Cancelable,
+    completion::{CompletionItem, completions},
+    CrateId, db, Diagnostic, FileId, FilePosition, FileSystemEdit,
+    Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit,
+    symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase},
 };
 
 #[derive(Debug, Default)]
@@ -366,7 +367,7 @@ impl AnalysisImpl {
                 range: d.range,
                 message: d.msg,
                 severity: d.severity,
-                fix: None,
+                fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)),
             })
             .collect::<Vec<_>>();
         if let Some(m) = source_binder::module_from_file_id(&*self.db, file_id)? {
@@ -425,25 +426,14 @@ impl AnalysisImpl {
         let file = self.file_syntax(file_id);
         let offset = range.start();
         let actions = vec![
-            (
-                "flip comma",
-                ra_editor::flip_comma(&file, offset).map(|f| f()),
-            ),
-            (
-                "add `#[derive]`",
-                ra_editor::add_derive(&file, offset).map(|f| f()),
-            ),
-            ("add impl", ra_editor::add_impl(&file, offset).map(|f| f())),
-            (
-                "introduce variable",
-                ra_editor::introduce_variable(&file, range).map(|f| f()),
-            ),
+            ra_editor::flip_comma(&file, offset).map(|f| f()),
+            ra_editor::add_derive(&file, offset).map(|f| f()),
+            ra_editor::add_impl(&file, offset).map(|f| f()),
+            ra_editor::introduce_variable(&file, range).map(|f| f()),
         ];
         actions
             .into_iter()
-            .filter_map(|(name, local_edit)| {
-                Some(SourceChange::from_local_edit(file_id, name, local_edit?))
-            })
+            .filter_map(|local_edit| Some(SourceChange::from_local_edit(file_id, local_edit?)))
             .collect()
     }
 
@@ -541,13 +531,13 @@ impl AnalysisImpl {
 }
 
 impl SourceChange {
-    pub(crate) fn from_local_edit(file_id: FileId, label: &str, edit: LocalEdit) -> SourceChange {
+    pub(crate) fn from_local_edit(file_id: FileId, edit: LocalEdit) -> SourceChange {
         let file_edit = SourceFileEdit {
             file_id,
             edit: edit.edit,
         };
         SourceChange {
-            label: label.to_string(),
+            label: edit.label,
             source_file_edits: vec![file_edit],
             file_system_edits: vec![],
             cursor_position: edit
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index a029f66b42d..476d1b438f6 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -288,19 +288,18 @@ impl Analysis {
     }
     pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange {
         let file = self.imp.file_syntax(file_id);
-        SourceChange::from_local_edit(file_id, "join lines", ra_editor::join_lines(&file, range))
+        SourceChange::from_local_edit(file_id, ra_editor::join_lines(&file, range))
     }
     pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> {
         let file = self.imp.file_syntax(position.file_id);
         let edit = ra_editor::on_enter(&file, position.offset)?;
-        let res = SourceChange::from_local_edit(position.file_id, "on enter", edit);
+        let res = SourceChange::from_local_edit(position.file_id, edit);
         Some(res)
     }
     pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> {
         let file = self.imp.file_syntax(position.file_id);
         Some(SourceChange::from_local_edit(
             position.file_id,
-            "add semicolon",
             ra_editor::on_eq_typed(&file, position.offset)?,
         ))
     }
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs
index 1d78cb7e81e..7615f37a60d 100644
--- a/crates/ra_editor/src/code_actions.rs
+++ b/crates/ra_editor/src/code_actions.rs
@@ -12,6 +12,7 @@ use crate::{find_node_at_offset, TextEdit, TextEditBuilder};
 
 #[derive(Debug)]
 pub struct LocalEdit {
+    pub label: String,
     pub edit: TextEdit,
     pub cursor_position: Option<TextUnit>,
 }
@@ -30,6 +31,7 @@ pub fn flip_comma<'a>(
         edit.replace(prev.range(), next.text().to_string());
         edit.replace(next.range(), prev.text().to_string());
         LocalEdit {
+            label: "flip comma".to_string(),
             edit: edit.finish(),
             cursor_position: None,
         }
@@ -58,6 +60,7 @@ pub fn add_derive<'a>(
             Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
         };
         LocalEdit {
+            label: "add `#[derive]`".to_string(),
             edit: edit.finish(),
             cursor_position: Some(offset),
         }
@@ -109,6 +112,7 @@ pub fn add_impl<'a>(
         buf.push_str("\n}");
         edit.insert(start_offset, buf);
         LocalEdit {
+            label: "add impl".to_string(),
             edit: edit.finish(),
             cursor_position: Some(offset),
         }
@@ -148,6 +152,7 @@ pub fn introduce_variable<'a>(
         }
         let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
         LocalEdit {
+            label: "introduce variable".to_string(),
             edit: edit.finish(),
             cursor_position: Some(cursor_position),
         }
@@ -194,6 +199,7 @@ pub fn make_pub_crate<'a>(
             || parent.children().any(|child| child.kind() == VISIBILITY)
         {
             return LocalEdit {
+                label: "make pub crate".to_string(),
                 edit: edit.finish(),
                 cursor_position: Some(offset),
             };
@@ -201,6 +207,7 @@ pub fn make_pub_crate<'a>(
 
         edit.insert(node_start, "pub(crate) ".to_string());
         LocalEdit {
+            label: "make pub crate".to_string(),
             edit: edit.finish(),
             cursor_position: Some(node_start),
         }
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index 7a689b0f25e..898d9b8c7ba 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -42,6 +42,7 @@ pub struct Diagnostic {
     pub range: TextRange,
     pub msg: String,
     pub severity: Severity,
+    pub fix: Option<LocalEdit>,
 }
 
 #[derive(Debug)]
@@ -111,6 +112,7 @@ pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> {
             range: location_to_range(err.location()),
             msg: format!("Syntax Error: {}", err),
             severity: Severity::Error,
+            fix: None,
         })
         .collect();
 
@@ -124,11 +126,27 @@ fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagn
     let mut diagnostics = Vec::new();
     for node in file.syntax().descendants() {
         if let Some(use_tree_list) = ast::UseTreeList::cast(node) {
-            if use_tree_list.use_trees().count() <= 1 {
+            if use_tree_list.use_trees().count() == 1 {
+                let range = use_tree_list.syntax().range();
+                // use_tree_list always has one child, so we use unwrap directly here.
+                let to_replace = typing::single_use_tree(use_tree_list)
+                    .unwrap()
+                    .syntax()
+                    .text()
+                    .to_string();
+                let mut edit_builder = TextEditBuilder::new();
+                edit_builder.delete(range);
+                edit_builder.insert(range.start(), to_replace);
+
                 diagnostics.push(Diagnostic {
-                    range: use_tree_list.syntax().range(),
+                    range: range,
                     msg: format!("Unnecessary braces in use statement"),
                     severity: Severity::WeakWarning,
+                    fix: Some(LocalEdit {
+                        label: "Remove unnecessary braces".to_string(),
+                        edit: edit_builder.finish(),
+                        cursor_position: None,
+                    }),
                 })
             }
         }
@@ -250,9 +268,7 @@ fn main() {}
         );
         let diagnostics = check_unnecessary_braces_in_use_statement(&file);
         assert_eq_dbg(
-            r#"[Diagnostic { range: [12; 15), msg: "Unnecessary braces in use statement", severity: WeakWarning },
-                Diagnostic { range: [24; 27), msg: "Unnecessary braces in use statement", severity: WeakWarning },
-                Diagnostic { range: [61; 64), msg: "Unnecessary braces in use statement", severity: WeakWarning }]"#,
+            "[Diagnostic { range: [12; 15), msg: \"Unnecessary braces in use statement\", severity: WeakWarning, fix: Some(LocalEdit { label: \"Remove unnecessary braces\", edit: TextEdit { atoms: [AtomTextEdit { delete: [12; 12), insert: \"b\" }, AtomTextEdit { delete: [12; 15), insert: \"\" }] }, cursor_position: Some(12) }) }, Diagnostic { range: [24; 27), msg: \"Unnecessary braces in use statement\", severity: WeakWarning, fix: Some(LocalEdit { label: \"Remove unnecessary braces\", edit: TextEdit { atoms: [AtomTextEdit { delete: [24; 24), insert: \"c\" }, AtomTextEdit { delete: [24; 27), insert: \"\" }] }, cursor_position: Some(24) }) }, Diagnostic { range: [61; 64), msg: \"Unnecessary braces in use statement\", severity: WeakWarning, fix: Some(LocalEdit { label: \"Remove unnecessary braces\", edit: TextEdit { atoms: [AtomTextEdit { delete: [61; 61), insert: \"e\" }, AtomTextEdit { delete: [61; 64), insert: \"\" }] }, cursor_position: Some(61) }) }]",
             &diagnostics,
         )
     }
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 5e412bcfa08..dcc8793afc2 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -8,7 +8,9 @@ use ra_syntax::{
     SyntaxKind::*,
     SyntaxNodeRef, TextRange, TextUnit,
 };
-use ra_text_edit::text_utils::contains_offset_nonstrict;
+use ra_text_edit::text_utils::{
+    contains_offset_nonstrict
+};
 
 use crate::{find_node_at_offset, TextEditBuilder, LocalEdit};
 
@@ -19,6 +21,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
         let pos = match text.find('\n') {
             None => {
                 return LocalEdit {
+                    label: "join lines".to_string(),
                     edit: TextEditBuilder::new().finish(),
                     cursor_position: None,
                 };
@@ -51,6 +54,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
     }
 
     LocalEdit {
+        label: "join lines".to_string(),
         edit: edit.finish(),
         cursor_position: None,
     }
@@ -76,6 +80,7 @@ pub fn on_enter(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> {
     let mut edit = TextEditBuilder::new();
     edit.insert(offset, inserted);
     Some(LocalEdit {
+        label: "on enter".to_string(),
         edit: edit.finish(),
         cursor_position: Some(cursor_position),
     })
@@ -126,6 +131,7 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit>
     let mut edit = TextEditBuilder::new();
     edit.insert(offset, ";".to_string());
     Some(LocalEdit {
+        label: "add semicolon".to_string(),
         edit: edit.finish(),
         cursor_position: None,
     })
@@ -248,24 +254,13 @@ fn join_single_use_tree(edit: &mut TextEditBuilder, node: SyntaxNodeRef) -> Opti
     Some(())
 }
 
-fn single_use_tree(tree_list: ast::UseTreeList) -> Option<ast::UseTree> {
-    let mut res = None;
-    for child in tree_list.syntax().children() {
-        if let Some(tree) = ast::UseTree::cast(child) {
-            if tree.syntax().text().contains('\n') {
-                return None;
-            }
-            if mem::replace(&mut res, Some(tree)).is_some() {
-                return None;
-            }
-        } else {
-            match child.kind() {
-                WHITESPACE | L_CURLY | R_CURLY | COMMA => (),
-                _ => return None,
-            }
-        }
+pub(crate) fn single_use_tree(tree_list: ast::UseTreeList) -> Option<ast::UseTree> {
+    let sub_use_trees = tree_list.use_trees().count();
+    if sub_use_trees != 1 {
+        return None;
     }
-    res
+
+    tree_list.use_trees().next()
 }
 
 fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {