aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs103
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs90
2 files changed, 130 insertions, 63 deletions
diff --git a/crates/ra_assists/src/handlers/extract_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
index 481baf1a4..098adf078 100644
--- a/crates/ra_assists/src/handlers/extract_variable.rs
+++ b/crates/ra_assists/src/handlers/extract_variable.rs
@@ -2,7 +2,6 @@ use ra_syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 SyntaxKind::{ 3 SyntaxKind::{
4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, 4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
5 WHITESPACE,
6 }, 5 },
7 SyntaxNode, 6 SyntaxNode,
8}; 7};
@@ -36,22 +35,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
36 mark::hit!(extract_var_in_comment_is_not_applicable); 35 mark::hit!(extract_var_in_comment_is_not_applicable);
37 return None; 36 return None;
38 } 37 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 38 let to_extract = node.ancestors().find_map(valid_target_expr)?;
40 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; 39 let anchor = Anchor::from(&to_extract)?;
41 let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); 40 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
42 if indent.kind() != WHITESPACE { 41 let target = to_extract.syntax().text_range();
43 return None;
44 }
45 let target = expr.syntax().text_range();
46 acc.add( 42 acc.add(
47 AssistId("extract_variable", AssistKind::RefactorExtract), 43 AssistId("extract_variable", AssistKind::RefactorExtract),
48 "Extract into variable", 44 "Extract into variable",
49 target, 45 target,
50 move |edit| { 46 move |edit| {
51 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { 47 let field_shorthand =
52 Some(field) => field.name_ref(), 48 match to_extract.syntax().parent().and_then(ast::RecordField::cast) {
53 None => None, 49 Some(field) => field.name_ref(),
54 }; 50 None => None,
51 };
55 52
56 let mut buf = String::new(); 53 let mut buf = String::new();
57 54
@@ -60,26 +57,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
60 None => "var_name".to_string(), 57 None => "var_name".to_string(),
61 }; 58 };
62 let expr_range = match &field_shorthand { 59 let expr_range = match &field_shorthand {
63 Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), 60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
64 None => expr.syntax().text_range(), 61 None => to_extract.syntax().text_range(),
65 }; 62 };
66 63
67 if wrap_in_block { 64 if let Anchor::WrapInBlock(_) = anchor {
68 format_to!(buf, "{{ let {} = ", var_name); 65 format_to!(buf, "{{ let {} = ", var_name);
69 } else { 66 } else {
70 format_to!(buf, "let {} = ", var_name); 67 format_to!(buf, "let {} = ", var_name);
71 }; 68 };
72 format_to!(buf, "{}", expr.syntax()); 69 format_to!(buf, "{}", to_extract.syntax());
73 70
74 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 71 if let Anchor::Replace(stmt) = anchor {
75 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
76 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
77 } else {
78 false
79 };
80 if is_full_stmt {
81 mark::hit!(test_extract_var_expr_stmt); 72 mark::hit!(test_extract_var_expr_stmt);
82 if full_stmt.unwrap().semicolon_token().is_none() { 73 if stmt.semicolon_token().is_none() {
83 buf.push_str(";"); 74 buf.push_str(";");
84 } 75 }
85 match ctx.config.snippet_cap { 76 match ctx.config.snippet_cap {
@@ -107,7 +98,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
107 } 98 }
108 99
109 edit.replace(expr_range, var_name.clone()); 100 edit.replace(expr_range, var_name.clone());
110 let offset = anchor_stmt.text_range().start(); 101 let offset = anchor.syntax().text_range().start();
111 match ctx.config.snippet_cap { 102 match ctx.config.snippet_cap {
112 Some(cap) => { 103 Some(cap) => {
113 let snip = 104 let snip =
@@ -117,8 +108,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
117 None => edit.insert(offset, buf), 108 None => edit.insert(offset, buf),
118 } 109 }
119 110
120 if wrap_in_block { 111 if let Anchor::WrapInBlock(_) = anchor {
121 edit.insert(anchor_stmt.text_range().end(), " }"); 112 edit.insert(anchor.syntax().text_range().end(), " }");
122 } 113 }
123 }, 114 },
124 ) 115 )
@@ -138,32 +129,48 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
138 } 129 }
139} 130}
140 131
141/// Returns the syntax node which will follow the freshly extractd var 132enum Anchor {
142/// and a boolean indicating whether we have to wrap it within a { } block 133 Before(SyntaxNode),
143/// to produce correct code. 134 Replace(ast::ExprStmt),
144/// It can be a statement, the last in a block expression or a wanna be block 135 WrapInBlock(SyntaxNode),
145/// expression like a lambda or match arm. 136}
146fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { 137
147 expr.syntax().ancestors().find_map(|node| { 138impl Anchor {
148 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 139 fn from(to_extract: &ast::Expr) -> Option<Anchor> {
149 if expr.syntax() == &node { 140 to_extract.syntax().ancestors().find_map(|node| {
150 mark::hit!(test_extract_var_last_expr); 141 if let Some(expr) =
151 return Some((node, false)); 142 node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr())
143 {
144 if expr.syntax() == &node {
145 mark::hit!(test_extract_var_last_expr);
146 return Some(Anchor::Before(node));
147 }
152 } 148 }
153 }
154 149
155 if let Some(parent) = node.parent() { 150 if let Some(parent) = node.parent() {
156 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { 151 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
157 return Some((node, true)); 152 return Some(Anchor::WrapInBlock(node));
153 }
158 } 154 }
159 }
160 155
161 if ast::Stmt::cast(node.clone()).is_some() { 156 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
162 return Some((node, false)); 157 if let ast::Stmt::ExprStmt(stmt) = stmt {
163 } 158 if stmt.expr().as_ref() == Some(to_extract) {
159 return Some(Anchor::Replace(stmt));
160 }
161 }
162 return Some(Anchor::Before(node));
163 }
164 None
165 })
166 }
164 167
165 None 168 fn syntax(&self) -> &SyntaxNode {
166 }) 169 match self {
170 Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
171 Anchor::Replace(stmt) => stmt.syntax(),
172 }
173 }
167} 174}
168 175
169#[cfg(test)] 176#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
index e212557c8..1d3ed3c6a 100644
--- a/crates/ra_assists/src/handlers/fix_visibility.rs
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -3,6 +3,7 @@ use ra_db::FileId;
3use ra_syntax::{ast, AstNode, TextRange, TextSize}; 3use ra_syntax::{ast, AstNode, TextRange, TextSize};
4 4
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; 5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
6 7
7// FIXME: this really should be a fix for diagnostic, rather than an assist. 8// FIXME: this really should be a fix for diagnostic, rather than an assist.
8 9
@@ -48,7 +49,8 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
48 return None; 49 return None;
49 }; 50 };
50 51
51 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?; 52 let (offset, current_visibility, target, target_file, target_name) =
53 target_data_for_def(ctx.db(), def)?;
52 54
53 let missing_visibility = 55 let missing_visibility =
54 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; 56 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
@@ -61,8 +63,20 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
61 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| { 63 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
62 builder.edit_file(target_file); 64 builder.edit_file(target_file);
63 match ctx.config.snippet_cap { 65 match ctx.config.snippet_cap {
64 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 66 Some(cap) => match current_visibility {
65 None => builder.insert(offset, format!("{} ", missing_visibility)), 67 Some(current_visibility) => builder.replace_snippet(
68 cap,
69 current_visibility.syntax().text_range(),
70 format!("$0{}", missing_visibility),
71 ),
72 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
73 },
74 None => match current_visibility {
75 Some(current_visibility) => {
76 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
77 }
78 None => builder.insert(offset, format!("{} ", missing_visibility)),
79 },
66 } 80 }
67 }) 81 })
68} 82}
@@ -82,14 +96,14 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
82 let target_module = parent.module(ctx.db()); 96 let target_module = parent.module(ctx.db());
83 97
84 let in_file_source = record_field_def.source(ctx.db()); 98 let in_file_source = record_field_def.source(ctx.db());
85 let (offset, target) = match in_file_source.value { 99 let (offset, current_visibility, target) = match in_file_source.value {
86 hir::FieldSource::Named(it) => { 100 hir::FieldSource::Named(it) => {
87 let s = it.syntax(); 101 let s = it.syntax();
88 (vis_offset(s), s.text_range()) 102 (vis_offset(s), it.visibility(), s.text_range())
89 } 103 }
90 hir::FieldSource::Pos(it) => { 104 hir::FieldSource::Pos(it) => {
91 let s = it.syntax(); 105 let s = it.syntax();
92 (vis_offset(s), s.text_range()) 106 (vis_offset(s), it.visibility(), s.text_range())
93 } 107 }
94 }; 108 };
95 109
@@ -104,8 +118,20 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
104 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| { 118 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
105 builder.edit_file(target_file); 119 builder.edit_file(target_file);
106 match ctx.config.snippet_cap { 120 match ctx.config.snippet_cap {
107 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 121 Some(cap) => match current_visibility {
108 None => builder.insert(offset, format!("{} ", missing_visibility)), 122 Some(current_visibility) => builder.replace_snippet(
123 cap,
124 dbg!(current_visibility.syntax()).text_range(),
125 format!("$0{}", missing_visibility),
126 ),
127 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
128 },
129 None => match current_visibility {
130 Some(current_visibility) => {
131 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
132 }
133 None => builder.insert(offset, format!("{} ", missing_visibility)),
134 },
109 } 135 }
110 }) 136 })
111} 137}
@@ -113,24 +139,30 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
113fn target_data_for_def( 139fn target_data_for_def(
114 db: &dyn HirDatabase, 140 db: &dyn HirDatabase,
115 def: hir::ModuleDef, 141 def: hir::ModuleDef,
116) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { 142) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
117 fn offset_target_and_file_id<S, Ast>( 143 fn offset_target_and_file_id<S, Ast>(
118 db: &dyn HirDatabase, 144 db: &dyn HirDatabase,
119 x: S, 145 x: S,
120 ) -> (TextSize, TextRange, FileId) 146 ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
121 where 147 where
122 S: HasSource<Ast = Ast>, 148 S: HasSource<Ast = Ast>,
123 Ast: AstNode, 149 Ast: AstNode + ast::VisibilityOwner,
124 { 150 {
125 let source = x.source(db); 151 let source = x.source(db);
126 let in_file_syntax = source.syntax(); 152 let in_file_syntax = source.syntax();
127 let file_id = in_file_syntax.file_id; 153 let file_id = in_file_syntax.file_id;
128 let syntax = in_file_syntax.value; 154 let syntax = in_file_syntax.value;
129 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) 155 let current_visibility = source.value.visibility();
156 (
157 vis_offset(syntax),
158 current_visibility,
159 syntax.text_range(),
160 file_id.original_file(db.upcast()),
161 )
130 } 162 }
131 163
132 let target_name; 164 let target_name;
133 let (offset, target, target_file) = match def { 165 let (offset, current_visibility, target, target_file) = match def {
134 hir::ModuleDef::Function(f) => { 166 hir::ModuleDef::Function(f) => {
135 target_name = Some(f.name(db)); 167 target_name = Some(f.name(db));
136 offset_target_and_file_id(db, f) 168 offset_target_and_file_id(db, f)
@@ -164,13 +196,13 @@ fn target_data_for_def(
164 let in_file_source = m.declaration_source(db)?; 196 let in_file_source = m.declaration_source(db)?;
165 let file_id = in_file_source.file_id.original_file(db.upcast()); 197 let file_id = in_file_source.file_id.original_file(db.upcast());
166 let syntax = in_file_source.value.syntax(); 198 let syntax = in_file_source.value.syntax();
167 (vis_offset(syntax), syntax.text_range(), file_id) 199 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
168 } 200 }
169 // Enum variants can't be private, we can't modify builtin types 201 // Enum variants can't be private, we can't modify builtin types
170 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, 202 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
171 }; 203 };
172 204
173 Some((offset, target, target_file, target_name)) 205 Some((offset, current_visibility, target, target_file, target_name))
174} 206}
175 207
176#[cfg(test)] 208#[cfg(test)]
@@ -523,6 +555,34 @@ struct Bar;
523 } 555 }
524 556
525 #[test] 557 #[test]
558 fn replaces_pub_crate_with_pub() {
559 check_assist(
560 fix_visibility,
561 r"
562//- /main.rs crate:a deps:foo
563foo::Bar<|>
564//- /lib.rs crate:foo
565pub(crate) struct Bar;
566",
567 r"$0pub struct Bar;
568",
569 );
570 check_assist(
571 fix_visibility,
572 r"
573//- /main.rs crate:a deps:foo
574fn main() {
575 foo::Foo { <|>bar: () };
576}
577//- /lib.rs crate:foo
578pub struct Foo { pub(crate) bar: () }
579",
580 r"pub struct Foo { $0pub bar: () }
581",
582 );
583 }
584
585 #[test]
526 #[ignore] 586 #[ignore]
527 // FIXME handle reexports properly 587 // FIXME handle reexports properly
528 fn fix_visibility_of_reexport() { 588 fn fix_visibility_of_reexport() {