@@ -6,11 +6,16 @@ use ide_db::{
6
6
defs:: { classify_name, classify_name_ref, Definition , NameClass , NameRefClass } ,
7
7
RootDatabase ,
8
8
} ;
9
- use std:: convert:: TryInto ;
9
+
10
+ use std:: {
11
+ convert:: TryInto ,
12
+ error:: Error ,
13
+ fmt:: { self , Display } ,
14
+ } ;
10
15
use syntax:: {
11
16
algo:: find_node_at_offset,
12
17
ast:: { self , NameOwner } ,
13
- lex_single_valid_syntax_kind , match_ast, AstNode , SyntaxKind , SyntaxNode , SyntaxToken ,
18
+ lex_single_syntax_kind , match_ast, AstNode , SyntaxKind , SyntaxNode , SyntaxToken ,
14
19
} ;
15
20
use test_utils:: mark;
16
21
use text_edit:: TextEdit ;
@@ -20,17 +25,37 @@ use crate::{
20
25
SourceChange , SourceFileEdit , TextRange , TextSize ,
21
26
} ;
22
27
28
+ #[ derive( Debug ) ]
29
+ pub struct RenameError ( pub ( crate ) String ) ;
30
+
31
+ impl fmt:: Display for RenameError {
32
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
33
+ Display :: fmt ( & self . 0 , f)
34
+ }
35
+ }
36
+
37
+ impl Error for RenameError { }
38
+
23
39
pub ( crate ) fn rename (
24
40
db : & RootDatabase ,
25
41
position : FilePosition ,
26
42
new_name : & str ,
27
- ) -> Option < RangeInfo < SourceChange > > {
43
+ ) -> Result < RangeInfo < SourceChange > , RenameError > {
28
44
let sema = Semantics :: new ( db) ;
29
45
30
- match lex_single_valid_syntax_kind ( new_name) ? {
31
- SyntaxKind :: IDENT | SyntaxKind :: UNDERSCORE => ( ) ,
32
- SyntaxKind :: SELF_KW => return rename_to_self ( & sema, position) ,
33
- _ => return None ,
46
+ match lex_single_syntax_kind ( new_name) {
47
+ Some ( res) => match res {
48
+ ( SyntaxKind :: IDENT , _) => ( ) ,
49
+ ( SyntaxKind :: UNDERSCORE , _) => ( ) ,
50
+ ( SyntaxKind :: SELF_KW , _) => return rename_to_self ( & sema, position) ,
51
+ ( _, Some ( syntax_error) ) => {
52
+ return Err ( RenameError ( format ! ( "Invalid name `{}`: {}" , new_name, syntax_error) ) )
53
+ }
54
+ ( _, None ) => {
55
+ return Err ( RenameError ( format ! ( "Invalid name `{}`: not an identifier" , new_name) ) )
56
+ }
57
+ } ,
58
+ None => return Err ( RenameError ( format ! ( "Invalid name `{}`: not an identifier" , new_name) ) ) ,
34
59
}
35
60
36
61
let source_file = sema. parse ( position. file_id ) ;
@@ -103,7 +128,7 @@ fn rename_mod(
103
128
position : FilePosition ,
104
129
module : Module ,
105
130
new_name : & str ,
106
- ) -> Option < RangeInfo < SourceChange > > {
131
+ ) -> Result < RangeInfo < SourceChange > , RenameError > {
107
132
let mut source_file_edits = Vec :: new ( ) ;
108
133
let mut file_system_edits = Vec :: new ( ) ;
109
134
@@ -125,51 +150,56 @@ fn rename_mod(
125
150
126
151
if let Some ( src) = module. declaration_source ( sema. db ) {
127
152
let file_id = src. file_id . original_file ( sema. db ) ;
128
- let name = src. value . name ( ) ? ;
153
+ let name = src. value . name ( ) . unwrap ( ) ;
129
154
let edit = SourceFileEdit {
130
155
file_id,
131
156
edit : TextEdit :: replace ( name. syntax ( ) . text_range ( ) , new_name. into ( ) ) ,
132
157
} ;
133
158
source_file_edits. push ( edit) ;
134
159
}
135
160
136
- let RangeInfo { range, info : refs } = find_all_refs ( sema, position, None ) ?;
161
+ let RangeInfo { range, info : refs } = find_all_refs ( sema, position, None )
162
+ . ok_or_else ( || RenameError ( "No references found at position" . to_string ( ) ) ) ?;
137
163
let ref_edits = refs
138
164
. references
139
165
. into_iter ( )
140
166
. map ( |reference| source_edit_from_reference ( reference, new_name) ) ;
141
167
source_file_edits. extend ( ref_edits) ;
142
168
143
- Some ( RangeInfo :: new ( range, SourceChange :: from_edits ( source_file_edits, file_system_edits) ) )
169
+ Ok ( RangeInfo :: new ( range, SourceChange :: from_edits ( source_file_edits, file_system_edits) ) )
144
170
}
145
171
146
172
fn rename_to_self (
147
173
sema : & Semantics < RootDatabase > ,
148
174
position : FilePosition ,
149
- ) -> Option < RangeInfo < SourceChange > > {
175
+ ) -> Result < RangeInfo < SourceChange > , RenameError > {
150
176
let source_file = sema. parse ( position. file_id ) ;
151
177
let syn = source_file. syntax ( ) ;
152
178
153
- let fn_def = find_node_at_offset :: < ast:: Fn > ( syn, position. offset ) ?;
154
- let params = fn_def. param_list ( ) ?;
179
+ let fn_def = find_node_at_offset :: < ast:: Fn > ( syn, position. offset )
180
+ . ok_or_else ( || RenameError ( "No surrounding method declaration found" . to_string ( ) ) ) ?;
181
+ let params =
182
+ fn_def. param_list ( ) . ok_or_else ( || RenameError ( "Method has no parameters" . to_string ( ) ) ) ?;
155
183
if params. self_param ( ) . is_some ( ) {
156
- return None ; // method already has self param
184
+ return Err ( RenameError ( "Method already has a self parameter" . to_string ( ) ) ) ;
157
185
}
158
- let first_param = params. params ( ) . next ( ) ?;
186
+ let first_param =
187
+ params. params ( ) . next ( ) . ok_or_else ( || RenameError ( "Method has no parameters" . into ( ) ) ) ?;
159
188
let mutable = match first_param. ty ( ) {
160
189
Some ( ast:: Type :: RefType ( rt) ) => rt. mut_token ( ) . is_some ( ) ,
161
- _ => return None , // not renaming other types
190
+ _ => return Err ( RenameError ( "Not renaming other types" . to_string ( ) ) ) ,
162
191
} ;
163
192
164
- let RangeInfo { range, info : refs } = find_all_refs ( sema, position, None ) ?;
193
+ let RangeInfo { range, info : refs } = find_all_refs ( sema, position, None )
194
+ . ok_or_else ( || RenameError ( "No reference found at position" . to_string ( ) ) ) ?;
165
195
166
196
let param_range = first_param. syntax ( ) . text_range ( ) ;
167
197
let ( param_ref, usages) : ( Vec < Reference > , Vec < Reference > ) = refs
168
198
. into_iter ( )
169
199
. partition ( |reference| param_range. intersect ( reference. file_range . range ) . is_some ( ) ) ;
170
200
171
201
if param_ref. is_empty ( ) {
172
- return None ;
202
+ return Err ( RenameError ( "Parameter to rename not found" . to_string ( ) ) ) ;
173
203
}
174
204
175
205
let mut edits = usages
@@ -185,7 +215,7 @@ fn rename_to_self(
185
215
) ,
186
216
} ) ;
187
217
188
- Some ( RangeInfo :: new ( range, SourceChange :: from ( edits) ) )
218
+ Ok ( RangeInfo :: new ( range, SourceChange :: from ( edits) ) )
189
219
}
190
220
191
221
fn text_edit_from_self_param (
@@ -216,12 +246,13 @@ fn rename_self_to_param(
216
246
position : FilePosition ,
217
247
self_token : SyntaxToken ,
218
248
new_name : & str ,
219
- ) -> Option < RangeInfo < SourceChange > > {
249
+ ) -> Result < RangeInfo < SourceChange > , RenameError > {
220
250
let source_file = sema. parse ( position. file_id ) ;
221
251
let syn = source_file. syntax ( ) ;
222
252
223
253
let text = sema. db . file_text ( position. file_id ) ;
224
- let fn_def = find_node_at_offset :: < ast:: Fn > ( syn, position. offset ) ?;
254
+ let fn_def = find_node_at_offset :: < ast:: Fn > ( syn, position. offset )
255
+ . ok_or_else ( || RenameError ( "No surrounding method declaration found" . to_string ( ) ) ) ?;
225
256
let search_range = fn_def. syntax ( ) . text_range ( ) ;
226
257
227
258
let mut edits: Vec < SourceFileEdit > = vec ! [ ] ;
@@ -235,7 +266,8 @@ fn rename_self_to_param(
235
266
syn. token_at_offset ( offset) . find ( |t| t. kind ( ) == SyntaxKind :: SELF_KW )
236
267
{
237
268
let edit = if let Some ( ref self_param) = ast:: SelfParam :: cast ( usage. parent ( ) ) {
238
- text_edit_from_self_param ( syn, self_param, new_name) ?
269
+ text_edit_from_self_param ( syn, self_param, new_name)
270
+ . ok_or_else ( || RenameError ( "No target type found" . to_string ( ) ) ) ?
239
271
} else {
240
272
TextEdit :: replace ( usage. text_range ( ) , String :: from ( new_name) )
241
273
} ;
@@ -246,26 +278,29 @@ fn rename_self_to_param(
246
278
let range = ast:: SelfParam :: cast ( self_token. parent ( ) )
247
279
. map_or ( self_token. text_range ( ) , |p| p. syntax ( ) . text_range ( ) ) ;
248
280
249
- Some ( RangeInfo :: new ( range, SourceChange :: from ( edits) ) )
281
+ Ok ( RangeInfo :: new ( range, SourceChange :: from ( edits) ) )
250
282
}
251
283
252
284
fn rename_reference (
253
285
sema : & Semantics < RootDatabase > ,
254
286
position : FilePosition ,
255
287
new_name : & str ,
256
- ) -> Option < RangeInfo < SourceChange > > {
257
- let RangeInfo { range, info : refs } = find_all_refs ( sema, position, None ) ?;
288
+ ) -> Result < RangeInfo < SourceChange > , RenameError > {
289
+ let RangeInfo { range, info : refs } = match find_all_refs ( sema, position, None ) {
290
+ Some ( range_info) => range_info,
291
+ None => return Err ( RenameError ( "No references found at position" . to_string ( ) ) ) ,
292
+ } ;
258
293
259
294
let edit = refs
260
295
. into_iter ( )
261
296
. map ( |reference| source_edit_from_reference ( reference, new_name) )
262
297
. collect :: < Vec < _ > > ( ) ;
263
298
264
299
if edit. is_empty ( ) {
265
- return None ;
300
+ return Err ( RenameError ( "No references found at position" . to_string ( ) ) ) ;
266
301
}
267
302
268
- Some ( RangeInfo :: new ( range, SourceChange :: from ( edit) ) )
303
+ Ok ( RangeInfo :: new ( range, SourceChange :: from ( edit) ) )
269
304
}
270
305
271
306
#[ cfg( test) ]
@@ -280,25 +315,45 @@ mod tests {
280
315
fn check ( new_name : & str , ra_fixture_before : & str , ra_fixture_after : & str ) {
281
316
let ra_fixture_after = & trim_indent ( ra_fixture_after) ;
282
317
let ( analysis, position) = fixture:: position ( ra_fixture_before) ;
283
- let source_change = analysis. rename ( position, new_name) . unwrap ( ) ;
284
- let mut text_edit_builder = TextEdit :: builder ( ) ;
285
- let mut file_id: Option < FileId > = None ;
286
- if let Some ( change) = source_change {
287
- for edit in change. info . source_file_edits {
288
- file_id = Some ( edit. file_id ) ;
289
- for indel in edit. edit . into_iter ( ) {
290
- text_edit_builder. replace ( indel. delete , indel. insert ) ;
318
+ let rename_result = analysis
319
+ . rename ( position, new_name)
320
+ . unwrap_or_else ( |err| panic ! ( "Rename to '{}' was cancelled: {}" , new_name, err) ) ;
321
+ match rename_result {
322
+ Ok ( source_change) => {
323
+ let mut text_edit_builder = TextEdit :: builder ( ) ;
324
+ let mut file_id: Option < FileId > = None ;
325
+ for edit in source_change. info . source_file_edits {
326
+ file_id = Some ( edit. file_id ) ;
327
+ for indel in edit. edit . into_iter ( ) {
328
+ text_edit_builder. replace ( indel. delete , indel. insert ) ;
329
+ }
291
330
}
331
+ let mut result = analysis. file_text ( file_id. unwrap ( ) ) . unwrap ( ) . to_string ( ) ;
332
+ text_edit_builder. finish ( ) . apply ( & mut result) ;
333
+ assert_eq_text ! ( ra_fixture_after, & * result) ;
292
334
}
293
- }
294
- let mut result = analysis. file_text ( file_id. unwrap ( ) ) . unwrap ( ) . to_string ( ) ;
295
- text_edit_builder. finish ( ) . apply ( & mut result) ;
296
- assert_eq_text ! ( ra_fixture_after, & * result) ;
335
+ Err ( err) => {
336
+ if ra_fixture_after. starts_with ( "error:" ) {
337
+ let error_message = ra_fixture_after
338
+ . chars ( )
339
+ . into_iter ( )
340
+ . skip ( "error:" . len ( ) )
341
+ . collect :: < String > ( ) ;
342
+ assert_eq ! ( error_message. trim( ) , err. to_string( ) ) ;
343
+ return ;
344
+ } else {
345
+ panic ! ( "Rename to '{}' failed unexpectedly: {}" , new_name, err)
346
+ }
347
+ }
348
+ } ;
297
349
}
298
350
299
351
fn check_expect ( new_name : & str , ra_fixture : & str , expect : Expect ) {
300
352
let ( analysis, position) = fixture:: position ( ra_fixture) ;
301
- let source_change = analysis. rename ( position, new_name) . unwrap ( ) . unwrap ( ) ;
353
+ let source_change = analysis
354
+ . rename ( position, new_name)
355
+ . unwrap ( )
356
+ . expect ( "Expect returned RangeInfo to be Some, but was None" ) ;
302
357
expect. assert_debug_eq ( & source_change)
303
358
}
304
359
@@ -313,11 +368,30 @@ mod tests {
313
368
}
314
369
315
370
#[ test]
316
- fn test_rename_to_invalid_identifier ( ) {
317
- let ( analysis, position) = fixture:: position ( r#"fn main() { let i<|> = 1; }"# ) ;
318
- let new_name = "invalid!" ;
319
- let source_change = analysis. rename ( position, new_name) . unwrap ( ) ;
320
- assert ! ( source_change. is_none( ) ) ;
371
+ fn test_rename_to_invalid_identifier1 ( ) {
372
+ check (
373
+ "invalid!" ,
374
+ r#"fn main() { let i<|> = 1; }"# ,
375
+ "error: Invalid name `invalid!`: not an identifier" ,
376
+ ) ;
377
+ }
378
+
379
+ #[ test]
380
+ fn test_rename_to_invalid_identifier2 ( ) {
381
+ check (
382
+ "multiple tokens" ,
383
+ r#"fn main() { let i<|> = 1; }"# ,
384
+ "error: Invalid name `multiple tokens`: not an identifier" ,
385
+ ) ;
386
+ }
387
+
388
+ #[ test]
389
+ fn test_rename_to_invalid_identifier3 ( ) {
390
+ check (
391
+ "let" ,
392
+ r#"fn main() { let i<|> = 1; }"# ,
393
+ "error: Invalid name `let`: not an identifier" ,
394
+ ) ;
321
395
}
322
396
323
397
#[ test]
@@ -349,6 +423,15 @@ fn main() {
349
423
) ;
350
424
}
351
425
426
+ #[ test]
427
+ fn test_rename_unresolved_reference ( ) {
428
+ check (
429
+ "new_name" ,
430
+ r#"fn main() { let _ = unresolved_ref<|>; }"# ,
431
+ "error: No references found at position" ,
432
+ ) ;
433
+ }
434
+
352
435
#[ test]
353
436
fn test_rename_for_macro_args ( ) {
354
437
check (
0 commit comments