1
1
//! Diagnostic emitted for files that aren't part of any crate.
2
2
3
- use hir:: db:: DefDatabase ;
3
+ use std:: iter;
4
+
5
+ use hir:: { db:: DefDatabase , InFile , ModuleSource } ;
4
6
use ide_db:: {
5
7
base_db:: { FileId , FileLoader , SourceDatabase , SourceDatabaseExt } ,
6
8
source_change:: SourceChange ,
@@ -42,45 +44,106 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
42
44
43
45
let source_root = ctx. sema . db . source_root ( ctx. sema . db . file_source_root ( file_id) ) ;
44
46
let our_path = source_root. path_for_file ( & file_id) ?;
45
- let ( mut module_name, _) = our_path. name_and_extension ( ) ?;
46
-
47
- // Candidates to look for:
48
- // - `mod.rs`, `main.rs` and `lib.rs` in the same folder
49
- // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
50
47
let parent = our_path. parent ( ) ?;
51
- let paths = {
52
- let parent = if module_name == "mod" {
53
- // for mod.rs we need to actually look up one higher
54
- // and take the parent as our to be module name
55
- let ( name, _) = parent. name_and_extension ( ) ?;
56
- module_name = name;
57
- parent. parent ( ) ?
58
- } else {
59
- parent
60
- } ;
61
- let mut paths =
62
- vec ! [ parent. join( "mod.rs" ) ?, parent. join( "lib.rs" ) ?, parent. join( "main.rs" ) ?] ;
63
-
64
- // `submod/bla.rs` -> `submod.rs`
65
- let parent_mod = ( || {
66
- let ( name, _) = parent. name_and_extension ( ) ?;
67
- parent. parent ( ) ?. join ( & format ! ( "{name}.rs" ) )
68
- } ) ( ) ;
69
- paths. extend ( parent_mod) ;
70
- paths
48
+ let ( module_name, _) = our_path. name_and_extension ( ) ?;
49
+ let ( parent, module_name) = if module_name == "mod" {
50
+ // for mod.rs we need to actually look up one higher
51
+ // and take the parent as our to be module name
52
+ let ( name, _) = parent. name_and_extension ( ) ?;
53
+ ( parent. parent ( ) ?, name. to_owned ( ) )
54
+ } else {
55
+ ( parent, module_name. to_owned ( ) )
71
56
} ;
72
57
73
- for & parent_id in paths. iter ( ) . filter_map ( |path| source_root. file_for_path ( path) ) {
58
+ // check crate roots, i.e. main.rs, lib.rs, ...
59
+ ' outer: for & krate in & * ctx. sema . db . relevant_crates ( file_id) {
60
+ let crate_def_map = ctx. sema . db . crate_def_map ( krate) ;
61
+ if let Some ( root_file_id) = crate_def_map[ crate_def_map. root ( ) ] . origin . file_id ( ) {
62
+ if let Some ( path) = source_root. path_for_file ( & root_file_id) {
63
+ let parent2 = path. parent ( ) ?;
64
+ if let Some ( rel) = parent. strip_prefix ( & parent2) {
65
+ let mut current = & crate_def_map[ crate_def_map. root ( ) ] ;
66
+ for ele in rel. as_ref ( ) . components ( ) {
67
+ let seg = match ele {
68
+ std:: path:: Component :: Normal ( seg) => seg. to_str ( ) ?,
69
+ std:: path:: Component :: RootDir => continue ,
70
+ // shouldn't occur
71
+ _ => continue ' outer,
72
+ } ;
73
+ match current. children . iter ( ) . find ( |( name, _) | name. to_smol_str ( ) == seg) {
74
+ Some ( ( _, child) ) => {
75
+ current = & crate_def_map[ * child] ;
76
+ }
77
+ None => continue ' outer,
78
+ }
79
+ }
80
+ let InFile { file_id : parent_file_id, value : source } =
81
+ current. definition_source ( ctx. sema . db ) ;
82
+ if let Some ( parent_file_id) = parent_file_id. file_id ( ) {
83
+ return make_fixes (
84
+ ctx. sema . db ,
85
+ parent_file_id,
86
+ source,
87
+ & module_name,
88
+ file_id,
89
+ ) ;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ // build all parent paths of the form `../module_name/mod.rs` and `../module_name.rs`
96
+ let paths = iter:: successors ( Some ( parent. clone ( ) ) , |prev| prev. parent ( ) ) . filter_map ( |path| {
97
+ let parent = path. parent ( ) ?;
98
+ let ( name, _) = path. name_and_extension ( ) ?;
99
+ Some ( ( [ parent. join ( & format ! ( "{name}.rs" ) ) ?, path. join ( "mod.rs" ) ?] , name. to_owned ( ) ) )
100
+ } ) ;
101
+ let mut stack = vec ! [ ] ;
102
+ if let Some ( & parent_id) = paths
103
+ . inspect ( |( _, name) | stack. push ( name. clone ( ) ) )
104
+ . find_map ( |( paths, _) | paths. into_iter ( ) . find_map ( |path| source_root. file_for_path ( & path) ) )
105
+ {
106
+ stack. pop ( ) ;
74
107
for & krate in ctx. sema . db . relevant_crates ( parent_id) . iter ( ) {
75
108
let crate_def_map = ctx. sema . db . crate_def_map ( krate) ;
76
- for ( _, module) in crate_def_map. modules ( ) {
77
- if module. origin . is_inline ( ) {
78
- // We don't handle inline `mod parent {}`s, they use different paths.
79
- continue ;
80
- }
81
-
109
+ ' outer: for ( _, module) in crate_def_map. modules ( ) {
82
110
if module. origin . file_id ( ) == Some ( parent_id) {
83
- return make_fixes ( ctx. sema . db , parent_id, module_name, file_id) ;
111
+ if module. origin . is_inline ( ) {
112
+ continue ;
113
+ }
114
+ if stack. is_empty ( ) {
115
+ return make_fixes (
116
+ ctx. sema . db ,
117
+ parent_id,
118
+ module. definition_source ( ctx. sema . db ) . value ,
119
+ & module_name,
120
+ file_id,
121
+ ) ;
122
+ } else {
123
+ let mut current = module;
124
+ for s in stack. iter ( ) . rev ( ) {
125
+ match module. children . iter ( ) . find ( |( name, _) | name. to_smol_str ( ) == s) {
126
+ Some ( ( _, child) ) => {
127
+ current = & crate_def_map[ * child] ;
128
+ }
129
+ None => break ' outer,
130
+ }
131
+ }
132
+ let InFile { file_id : parent_file_id, value : source } =
133
+ current. definition_source ( ctx. sema . db ) ;
134
+ if let Some ( parent_file_id) = parent_file_id. file_id ( ) {
135
+ if current. origin . is_inline ( ) {
136
+ return make_fixes (
137
+ ctx. sema . db ,
138
+ parent_file_id,
139
+ source,
140
+ & module_name,
141
+ file_id,
142
+ ) ;
143
+ }
144
+ }
145
+ break ;
146
+ }
84
147
}
85
148
}
86
149
}
@@ -92,6 +155,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
92
155
fn make_fixes (
93
156
db : & RootDatabase ,
94
157
parent_file_id : FileId ,
158
+ source : ModuleSource ,
95
159
new_mod_name : & str ,
96
160
added_file_id : FileId ,
97
161
) -> Option < Vec < Assist > > {
@@ -102,14 +166,18 @@ fn make_fixes(
102
166
let mod_decl = format ! ( "mod {new_mod_name};" ) ;
103
167
let pub_mod_decl = format ! ( "pub mod {new_mod_name};" ) ;
104
168
105
- let ast: ast:: SourceFile = db. parse ( parent_file_id) . tree ( ) ;
106
-
107
169
let mut mod_decl_builder = TextEdit :: builder ( ) ;
108
170
let mut pub_mod_decl_builder = TextEdit :: builder ( ) ;
109
171
172
+ let mut items = match & source {
173
+ ModuleSource :: SourceFile ( it) => it. items ( ) ,
174
+ ModuleSource :: Module ( it) => it. item_list ( ) ?. items ( ) ,
175
+ ModuleSource :: BlockExpr ( _) => return None ,
176
+ } ;
177
+
110
178
// If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
111
179
// probably `#[cfg]`d out).
112
- for item in ast . items ( ) {
180
+ for item in items. clone ( ) {
113
181
if let ast:: Item :: Module ( m) = item {
114
182
if let Some ( name) = m. name ( ) {
115
183
if m. item_list ( ) . is_none ( ) && name. to_string ( ) == new_mod_name {
@@ -121,7 +189,7 @@ fn make_fixes(
121
189
}
122
190
123
191
// If there are existing `mod m;` items, append after them (after the first group of them, rather).
124
- match ast . items ( ) . skip_while ( |item| !is_outline_mod ( item) ) . take_while ( is_outline_mod) . last ( ) {
192
+ match items. clone ( ) . skip_while ( |item| !is_outline_mod ( item) ) . take_while ( is_outline_mod) . last ( ) {
125
193
Some ( last) => {
126
194
cov_mark:: hit!( unlinked_file_append_to_existing_mods) ;
127
195
let offset = last. syntax ( ) . text_range ( ) . end ( ) ;
@@ -130,7 +198,7 @@ fn make_fixes(
130
198
}
131
199
None => {
132
200
// Prepend before the first item in the file.
133
- match ast . items ( ) . next ( ) {
201
+ match items. next ( ) {
134
202
Some ( item) => {
135
203
cov_mark:: hit!( unlinked_file_prepend_before_first_item) ;
136
204
let offset = item. syntax ( ) . text_range ( ) . start ( ) ;
@@ -140,7 +208,13 @@ fn make_fixes(
140
208
None => {
141
209
// No items in the file, so just append at the end.
142
210
cov_mark:: hit!( unlinked_file_empty_file) ;
143
- let offset = ast. syntax ( ) . text_range ( ) . end ( ) ;
211
+ let offset = match & source {
212
+ ModuleSource :: SourceFile ( it) => it. syntax ( ) . text_range ( ) . end ( ) ,
213
+ ModuleSource :: Module ( it) => {
214
+ it. item_list ( ) ?. r_curly_token ( ) ?. text_range ( ) . start ( )
215
+ }
216
+ ModuleSource :: BlockExpr ( _) => return None ,
217
+ } ;
144
218
mod_decl_builder. insert ( offset, format ! ( "{mod_decl}\n " ) ) ;
145
219
pub_mod_decl_builder. insert ( offset, format ! ( "{pub_mod_decl}\n " ) ) ;
146
220
}
@@ -167,7 +241,6 @@ fn make_fixes(
167
241
168
242
#[ cfg( test) ]
169
243
mod tests {
170
-
171
244
use crate :: tests:: { check_diagnostics, check_fix, check_fixes, check_no_fix} ;
172
245
173
246
#[ test]
330
403
mod foo;
331
404
332
405
//- /foo.rs
406
+ "# ,
407
+ ) ;
408
+ }
409
+
410
+ #[ test]
411
+ fn unlinked_file_insert_into_inline_simple ( ) {
412
+ check_fix (
413
+ r#"
414
+ //- /main.rs
415
+ mod bar;
416
+ //- /bar.rs
417
+ mod foo {
418
+
419
+ }
420
+ //- /bar/foo/baz.rs
421
+ $0
422
+ "# ,
423
+ r#"
424
+ mod foo {
425
+
426
+ mod baz;
427
+ }
428
+ "# ,
429
+ ) ;
430
+ }
431
+
432
+ #[ test]
433
+ fn unlinked_file_insert_into_inline_simple_modrs ( ) {
434
+ check_fix (
435
+ r#"
436
+ //- /main.rs
437
+ mod bar;
438
+ //- /bar.rs
439
+ mod baz {
440
+
441
+ }
442
+ //- /bar/baz/foo/mod.rs
443
+ $0
444
+ "# ,
445
+ r#"
446
+ mod baz {
447
+
448
+ mod foo;
449
+ }
450
+ "# ,
451
+ ) ;
452
+ }
453
+
454
+ #[ test]
455
+ fn unlinked_file_insert_into_inline_simple_modrs_main ( ) {
456
+ check_fix (
457
+ r#"
458
+ //- /main.rs
459
+ mod bar {
460
+
461
+ }
462
+ //- /bar/foo/mod.rs
463
+ $0
464
+ "# ,
465
+ r#"
466
+ mod bar {
467
+
468
+ mod foo;
469
+ }
333
470
"# ,
334
471
) ;
335
472
}
0 commit comments