diff options
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/handlers/extract_variable.rs | 103 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/fix_visibility.rs | 90 |
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 | 132 | enum 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 | } |
146 | fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | 137 | |
147 | expr.syntax().ancestors().find_map(|node| { | 138 | impl 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; | |||
3 | use ra_syntax::{ast, AstNode, TextRange, TextSize}; | 3 | use ra_syntax::{ast, AstNode, TextRange, TextSize}; |
4 | 4 | ||
5 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; | 5 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
6 | use 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) -> | |||
113 | fn target_data_for_def( | 139 | fn 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 | ||
563 | foo::Bar<|> | ||
564 | //- /lib.rs crate:foo | ||
565 | pub(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 | ||
574 | fn main() { | ||
575 | foo::Foo { <|>bar: () }; | ||
576 | } | ||
577 | //- /lib.rs crate:foo | ||
578 | pub 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() { |