aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-18 03:02:34 +0100
committerGitHub <[email protected]>2021-05-18 03:02:34 +0100
commite3d0d89d7e3e253234271008df9324ec1faf1746 (patch)
tree13c4972204ac32dd1a1702c254ffef4b85a76bf3 /crates/ide/src/diagnostics
parentc04eaa1f37f31d7125372ba14da3d5059297e8b2 (diff)
parente0b01f34bb994ca8959f3040dbacafc6c56e4778 (diff)
Merge #8345
8345: Add pub mod option for UnlinkedFile r=rainy-me a=rainy-me close #8228 This is a draft that changes `Diagnostic` to contain multiple fixes. Pre analysis is in https://github.com/rust-analyzer/rust-analyzer/issues/8228#issuecomment-812887085 Because this solution is straightforward so I decided to type it out for discussion. Currently the `check_fix` is not able to test the situation when multiple fixes available. <del>Also because `Insert 'mod x;'` and `Insert 'pub mod x;'` are so similar, I don't know how to test them correctly and want some suggestions.</del>. I added `check_fixes` to allow checking mutiple possible fixes. In additional, instead of append after possible existing `mod y`, I think it's possible to Insert `pub mod x;` after `pub mod y`. Should I implement this too? Co-authored-by: rainy-me <[email protected]>
Diffstat (limited to 'crates/ide/src/diagnostics')
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs13
-rw-r--r--crates/ide/src/diagnostics/fixes.rs8
-rw-r--r--crates/ide/src/diagnostics/fixes/change_case.rs10
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs19
-rw-r--r--crates/ide/src/diagnostics/fixes/fill_missing_fields.rs12
-rw-r--r--crates/ide/src/diagnostics/fixes/remove_semicolon.rs10
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs12
-rw-r--r--crates/ide/src/diagnostics/fixes/unresolved_module.rs66
-rw-r--r--crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs10
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs54
10 files changed, 115 insertions, 99 deletions
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index 2b1787f9b..01bd2dba6 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -46,14 +46,13 @@ fn check_expr_field_shorthand(
46 46
47 let field_range = record_field.syntax().text_range(); 47 let field_range = record_field.syntax().text_range();
48 acc.push( 48 acc.push(
49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( 49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string())
50 Some(fix( 50 .with_fixes(Some(vec![fix(
51 "use_expr_field_shorthand", 51 "use_expr_field_shorthand",
52 "Use struct shorthand initialization", 52 "Use struct shorthand initialization",
53 SourceChange::from_text_edit(file_id, edit), 53 SourceChange::from_text_edit(file_id, edit),
54 field_range, 54 field_range,
55 )), 55 )])),
56 ),
57 ); 56 );
58 } 57 }
59} 58}
@@ -86,13 +85,13 @@ fn check_pat_field_shorthand(
86 let edit = edit_builder.finish(); 85 let edit = edit_builder.finish();
87 86
88 let field_range = record_pat_field.syntax().text_range(); 87 let field_range = record_pat_field.syntax().text_range();
89 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( 88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fixes(
90 Some(fix( 89 Some(vec![fix(
91 "use_pat_field_shorthand", 90 "use_pat_field_shorthand",
92 "Use struct field shorthand", 91 "Use struct field shorthand",
93 SourceChange::from_text_edit(file_id, edit), 92 SourceChange::from_text_edit(file_id, edit),
94 field_range, 93 field_range,
95 )), 94 )]),
96 )); 95 ));
97 } 96 }
98} 97}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 92b3f5a2d..258ac6974 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -14,18 +14,18 @@ use ide_db::RootDatabase;
14 14
15use crate::Assist; 15use crate::Assist;
16 16
17/// A [Diagnostic] that potentially has a fix available. 17/// A [Diagnostic] that potentially has some fixes available.
18/// 18///
19/// [Diagnostic]: hir::diagnostics::Diagnostic 19/// [Diagnostic]: hir::diagnostics::Diagnostic
20pub(crate) trait DiagnosticWithFix: Diagnostic { 20pub(crate) trait DiagnosticWithFixes: Diagnostic {
21 /// `resolve` determines if the diagnostic should fill in the `edit` field 21 /// `resolve` determines if the diagnostic should fill in the `edit` field
22 /// of the assist. 22 /// of the assist.
23 /// 23 ///
24 /// If `resolve` is false, the edit will be computed later, on demand, and 24 /// If `resolve` is false, the edit will be computed later, on demand, and
25 /// can be omitted. 25 /// can be omitted.
26 fn fix( 26 fn fixes(
27 &self, 27 &self,
28 sema: &Semantics<RootDatabase>, 28 sema: &Semantics<RootDatabase>,
29 _resolve: &AssistResolveStrategy, 29 _resolve: &AssistResolveStrategy,
30 ) -> Option<Assist>; 30 ) -> Option<Vec<Assist>>;
31} 31}
diff --git a/crates/ide/src/diagnostics/fixes/change_case.rs b/crates/ide/src/diagnostics/fixes/change_case.rs
index 80aca58a1..42be3375f 100644
--- a/crates/ide/src/diagnostics/fixes/change_case.rs
+++ b/crates/ide/src/diagnostics/fixes/change_case.rs
@@ -4,16 +4,16 @@ use ide_db::{base_db::FilePosition, RootDatabase};
4use syntax::AstNode; 4use syntax::AstNode;
5 5
6use crate::{ 6use crate::{
7 diagnostics::{unresolved_fix, DiagnosticWithFix}, 7 diagnostics::{unresolved_fix, DiagnosticWithFixes},
8 references::rename::rename_with_semantics, 8 references::rename::rename_with_semantics,
9}; 9};
10 10
11impl DiagnosticWithFix for IncorrectCase { 11impl DiagnosticWithFixes for IncorrectCase {
12 fn fix( 12 fn fixes(
13 &self, 13 &self,
14 sema: &Semantics<RootDatabase>, 14 sema: &Semantics<RootDatabase>,
15 resolve: &AssistResolveStrategy, 15 resolve: &AssistResolveStrategy,
16 ) -> Option<Assist> { 16 ) -> Option<Vec<Assist>> {
17 let root = sema.db.parse_or_expand(self.file)?; 17 let root = sema.db.parse_or_expand(self.file)?;
18 let name_node = self.ident.to_node(&root); 18 let name_node = self.ident.to_node(&root);
19 19
@@ -28,7 +28,7 @@ impl DiagnosticWithFix for IncorrectCase {
28 res.source_change = Some(source_change.ok().unwrap_or_default()); 28 res.source_change = Some(source_change.ok().unwrap_or_default());
29 } 29 }
30 30
31 Some(res) 31 Some(vec![res])
32 } 32 }
33} 33}
34 34
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
index 24e0fda52..a5f457dce 100644
--- a/crates/ide/src/diagnostics/fixes/create_field.rs
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -7,18 +7,17 @@ use syntax::{
7use text_edit::TextEdit; 7use text_edit::TextEdit;
8 8
9use crate::{ 9use crate::{
10 diagnostics::{fix, DiagnosticWithFix}, 10 diagnostics::{fix, DiagnosticWithFixes},
11 Assist, AssistResolveStrategy, 11 Assist, AssistResolveStrategy,
12}; 12};
13 13impl DiagnosticWithFixes for NoSuchField {
14impl DiagnosticWithFix for NoSuchField { 14 fn fixes(
15 fn fix(
16 &self, 15 &self,
17 sema: &Semantics<RootDatabase>, 16 sema: &Semantics<RootDatabase>,
18 _resolve: &AssistResolveStrategy, 17 _resolve: &AssistResolveStrategy,
19 ) -> Option<Assist> { 18 ) -> Option<Vec<Assist>> {
20 let root = sema.db.parse_or_expand(self.file)?; 19 let root = sema.db.parse_or_expand(self.file)?;
21 missing_record_expr_field_fix( 20 missing_record_expr_field_fixes(
22 &sema, 21 &sema,
23 self.file.original_file(sema.db), 22 self.file.original_file(sema.db),
24 &self.field.to_node(&root), 23 &self.field.to_node(&root),
@@ -26,11 +25,11 @@ impl DiagnosticWithFix for NoSuchField {
26 } 25 }
27} 26}
28 27
29fn missing_record_expr_field_fix( 28fn missing_record_expr_field_fixes(
30 sema: &Semantics<RootDatabase>, 29 sema: &Semantics<RootDatabase>,
31 usage_file_id: FileId, 30 usage_file_id: FileId,
32 record_expr_field: &ast::RecordExprField, 31 record_expr_field: &ast::RecordExprField,
33) -> Option<Assist> { 32) -> Option<Vec<Assist>> {
34 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; 33 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
35 let def_id = sema.resolve_variant(record_lit)?; 34 let def_id = sema.resolve_variant(record_lit)?;
36 let module; 35 let module;
@@ -89,12 +88,12 @@ fn missing_record_expr_field_fix(
89 TextEdit::insert(last_field_syntax.text_range().end(), new_field), 88 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
90 ); 89 );
91 90
92 return Some(fix( 91 return Some(vec![fix(
93 "create_field", 92 "create_field",
94 "Create field", 93 "Create field",
95 source_change, 94 source_change,
96 record_expr_field.syntax().text_range(), 95 record_expr_field.syntax().text_range(),
97 )); 96 )]);
98 97
99 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { 98 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
100 match field_def_list { 99 match field_def_list {
diff --git a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
index 37a0e37a9..b5dd64c08 100644
--- a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
+++ b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
@@ -5,16 +5,16 @@ use syntax::{algo, ast::make, AstNode};
5use text_edit::TextEdit; 5use text_edit::TextEdit;
6 6
7use crate::{ 7use crate::{
8 diagnostics::{fix, fixes::DiagnosticWithFix}, 8 diagnostics::{fix, fixes::DiagnosticWithFixes},
9 Assist, 9 Assist,
10}; 10};
11 11
12impl DiagnosticWithFix for MissingFields { 12impl DiagnosticWithFixes for MissingFields {
13 fn fix( 13 fn fixes(
14 &self, 14 &self,
15 sema: &Semantics<RootDatabase>, 15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy, 16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Assist> { 17 ) -> Option<Vec<Assist>> {
18 // Note that although we could add a diagnostics to 18 // Note that although we could add a diagnostics to
19 // fill the missing tuple field, e.g : 19 // fill the missing tuple field, e.g :
20 // `struct A(usize);` 20 // `struct A(usize);`
@@ -41,12 +41,12 @@ impl DiagnosticWithFix for MissingFields {
41 .into_text_edit(&mut builder); 41 .into_text_edit(&mut builder);
42 builder.finish() 42 builder.finish()
43 }; 43 };
44 Some(fix( 44 Some(vec![fix(
45 "fill_missing_fields", 45 "fill_missing_fields",
46 "Fill struct fields", 46 "Fill struct fields",
47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit), 47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
48 sema.original_range(&field_list_parent.syntax()).range, 48 sema.original_range(&field_list_parent.syntax()).range,
49 )) 49 )])
50 } 50 }
51} 51}
52 52
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
index 45471da41..f1724d479 100644
--- a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
+++ b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs
@@ -4,14 +4,14 @@ use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{ast, AstNode}; 4use syntax::{ast, AstNode};
5use text_edit::TextEdit; 5use text_edit::TextEdit;
6 6
7use crate::diagnostics::{fix, DiagnosticWithFix}; 7use crate::diagnostics::{fix, DiagnosticWithFixes};
8 8
9impl DiagnosticWithFix for RemoveThisSemicolon { 9impl DiagnosticWithFixes for RemoveThisSemicolon {
10 fn fix( 10 fn fixes(
11 &self, 11 &self,
12 sema: &Semantics<RootDatabase>, 12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy, 13 _resolve: &AssistResolveStrategy,
14 ) -> Option<Assist> { 14 ) -> Option<Vec<Assist>> {
15 let root = sema.db.parse_or_expand(self.file)?; 15 let root = sema.db.parse_or_expand(self.file)?;
16 16
17 let semicolon = self 17 let semicolon = self
@@ -26,7 +26,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
26 let edit = TextEdit::delete(semicolon); 26 let edit = TextEdit::delete(semicolon);
27 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 27 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
28 28
29 Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)) 29 Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)])
30 } 30 }
31} 31}
32 32
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
index b0ef7b44a..444bf563b 100644
--- a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
+++ b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
@@ -7,14 +7,14 @@ use syntax::{
7}; 7};
8use text_edit::TextEdit; 8use text_edit::TextEdit;
9 9
10use crate::diagnostics::{fix, DiagnosticWithFix}; 10use crate::diagnostics::{fix, DiagnosticWithFixes};
11 11
12impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 12impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap {
13 fn fix( 13 fn fixes(
14 &self, 14 &self,
15 sema: &Semantics<RootDatabase>, 15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy, 16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Assist> { 17 ) -> Option<Vec<Assist>> {
18 let root = sema.db.parse_or_expand(self.file)?; 18 let root = sema.db.parse_or_expand(self.file)?;
19 let next_expr = self.next_expr.to_node(&root); 19 let next_expr = self.next_expr.to_node(&root);
20 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; 20 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
@@ -32,12 +32,12 @@ impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
32 32
33 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 33 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
34 34
35 Some(fix( 35 Some(vec![fix(
36 "replace_with_find_map", 36 "replace_with_find_map",
37 "Replace filter_map(..).next() with find_map()", 37 "Replace filter_map(..).next() with find_map()",
38 source_change, 38 source_change,
39 trigger_range, 39 trigger_range,
40 )) 40 )])
41 } 41 }
42} 42}
43 43
diff --git a/crates/ide/src/diagnostics/fixes/unresolved_module.rs b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
index 81244b293..b3d0283bb 100644
--- a/crates/ide/src/diagnostics/fixes/unresolved_module.rs
+++ b/crates/ide/src/diagnostics/fixes/unresolved_module.rs
@@ -3,17 +3,17 @@ use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase}; 3use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase};
4use syntax::AstNode; 4use syntax::AstNode;
5 5
6use crate::diagnostics::{fix, DiagnosticWithFix}; 6use crate::diagnostics::{fix, DiagnosticWithFixes};
7 7
8impl DiagnosticWithFix for UnresolvedModule { 8impl DiagnosticWithFixes for UnresolvedModule {
9 fn fix( 9 fn fixes(
10 &self, 10 &self,
11 sema: &Semantics<RootDatabase>, 11 sema: &Semantics<RootDatabase>,
12 _resolve: &AssistResolveStrategy, 12 _resolve: &AssistResolveStrategy,
13 ) -> Option<Assist> { 13 ) -> Option<Vec<Assist>> {
14 let root = sema.db.parse_or_expand(self.file)?; 14 let root = sema.db.parse_or_expand(self.file)?;
15 let unresolved_module = self.decl.to_node(&root); 15 let unresolved_module = self.decl.to_node(&root);
16 Some(fix( 16 Some(vec![fix(
17 "create_module", 17 "create_module",
18 "Create module", 18 "Create module",
19 FileSystemEdit::CreateFile { 19 FileSystemEdit::CreateFile {
@@ -25,7 +25,7 @@ impl DiagnosticWithFix for UnresolvedModule {
25 } 25 }
26 .into(), 26 .into(),
27 unresolved_module.syntax().text_range(), 27 unresolved_module.syntax().text_range(),
28 )) 28 )])
29 } 29 }
30} 30}
31 31
@@ -45,33 +45,35 @@ mod tests {
45 message: "unresolved module", 45 message: "unresolved module",
46 range: 0..8, 46 range: 0..8,
47 severity: Error, 47 severity: Error,
48 fix: Some( 48 fixes: Some(
49 Assist { 49 [
50 id: AssistId( 50 Assist {
51 "create_module", 51 id: AssistId(
52 QuickFix, 52 "create_module",
53 ), 53 QuickFix,
54 label: "Create module", 54 ),
55 group: None, 55 label: "Create module",
56 target: 0..8, 56 group: None,
57 source_change: Some( 57 target: 0..8,
58 SourceChange { 58 source_change: Some(
59 source_file_edits: {}, 59 SourceChange {
60 file_system_edits: [ 60 source_file_edits: {},
61 CreateFile { 61 file_system_edits: [
62 dst: AnchoredPathBuf { 62 CreateFile {
63 anchor: FileId( 63 dst: AnchoredPathBuf {
64 0, 64 anchor: FileId(
65 ), 65 0,
66 path: "foo.rs", 66 ),
67 path: "foo.rs",
68 },
69 initial_contents: "",
67 }, 70 },
68 initial_contents: "", 71 ],
69 }, 72 is_snippet: false,
70 ], 73 },
71 is_snippet: false, 74 ),
72 }, 75 },
73 ), 76 ],
74 },
75 ), 77 ),
76 unused: false, 78 unused: false,
77 code: Some( 79 code: Some(
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
index 66676064a..715a403b9 100644
--- a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
+++ b/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs
@@ -4,14 +4,14 @@ use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::AstNode; 4use syntax::AstNode;
5use text_edit::TextEdit; 5use text_edit::TextEdit;
6 6
7use crate::diagnostics::{fix, DiagnosticWithFix}; 7use crate::diagnostics::{fix, DiagnosticWithFixes};
8 8
9impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 9impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr {
10 fn fix( 10 fn fixes(
11 &self, 11 &self,
12 sema: &Semantics<RootDatabase>, 12 sema: &Semantics<RootDatabase>,
13 _resolve: &AssistResolveStrategy, 13 _resolve: &AssistResolveStrategy,
14 ) -> Option<Assist> { 14 ) -> Option<Vec<Assist>> {
15 let root = sema.db.parse_or_expand(self.file)?; 15 let root = sema.db.parse_or_expand(self.file)?;
16 let tail_expr = self.expr.to_node(&root); 16 let tail_expr = self.expr.to_node(&root);
17 let tail_expr_range = tail_expr.syntax().text_range(); 17 let tail_expr_range = tail_expr.syntax().text_range();
@@ -19,7 +19,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
19 let edit = TextEdit::replace(tail_expr_range, replacement); 19 let edit = TextEdit::replace(tail_expr_range, replacement);
20 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 20 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
21 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; 21 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
22 Some(fix("wrap_tail_expr", name, source_change, tail_expr_range)) 22 Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)])
23 } 23 }
24} 24}
25 25
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 93fd25dea..51fe0f360 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -18,7 +18,7 @@ use syntax::{
18use text_edit::TextEdit; 18use text_edit::TextEdit;
19 19
20use crate::{ 20use crate::{
21 diagnostics::{fix, fixes::DiagnosticWithFix}, 21 diagnostics::{fix, fixes::DiagnosticWithFixes},
22 Assist, 22 Assist,
23}; 23};
24 24
@@ -50,13 +50,13 @@ impl Diagnostic for UnlinkedFile {
50 } 50 }
51} 51}
52 52
53impl DiagnosticWithFix for UnlinkedFile { 53impl DiagnosticWithFixes for UnlinkedFile {
54 fn fix( 54 fn fixes(
55 &self, 55 &self,
56 sema: &hir::Semantics<RootDatabase>, 56 sema: &hir::Semantics<RootDatabase>,
57 _resolve: &AssistResolveStrategy, 57 _resolve: &AssistResolveStrategy,
58 ) -> Option<Assist> { 58 ) -> Option<Vec<Assist>> {
59 // If there's an existing module that could add a `mod` item to include the unlinked file, 59 // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
60 // suggest that as a fix. 60 // suggest that as a fix.
61 61
62 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id)); 62 let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id));
@@ -90,7 +90,7 @@ impl DiagnosticWithFix for UnlinkedFile {
90 } 90 }
91 91
92 if module.origin.file_id() == Some(*parent_id) { 92 if module.origin.file_id() == Some(*parent_id) {
93 return make_fix(sema.db, *parent_id, module_name, self.file_id); 93 return make_fixes(sema.db, *parent_id, module_name, self.file_id);
94 } 94 }
95 } 95 }
96 } 96 }
@@ -101,20 +101,23 @@ impl DiagnosticWithFix for UnlinkedFile {
101 } 101 }
102} 102}
103 103
104fn make_fix( 104fn make_fixes(
105 db: &RootDatabase, 105 db: &RootDatabase,
106 parent_file_id: FileId, 106 parent_file_id: FileId,
107 new_mod_name: &str, 107 new_mod_name: &str,
108 added_file_id: FileId, 108 added_file_id: FileId,
109) -> Option<Assist> { 109) -> Option<Vec<Assist>> {
110 fn is_outline_mod(item: &ast::Item) -> bool { 110 fn is_outline_mod(item: &ast::Item) -> bool {
111 matches!(item, ast::Item::Module(m) if m.item_list().is_none()) 111 matches!(item, ast::Item::Module(m) if m.item_list().is_none())
112 } 112 }
113 113
114 let mod_decl = format!("mod {};", new_mod_name); 114 let mod_decl = format!("mod {};", new_mod_name);
115 let pub_mod_decl = format!("pub mod {};", new_mod_name);
116
115 let ast: ast::SourceFile = db.parse(parent_file_id).tree(); 117 let ast: ast::SourceFile = db.parse(parent_file_id).tree();
116 118
117 let mut builder = TextEdit::builder(); 119 let mut mod_decl_builder = TextEdit::builder();
120 let mut pub_mod_decl_builder = TextEdit::builder();
118 121
119 // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's 122 // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
120 // probably `#[cfg]`d out). 123 // probably `#[cfg]`d out).
@@ -138,30 +141,43 @@ fn make_fix(
138 { 141 {
139 Some(last) => { 142 Some(last) => {
140 cov_mark::hit!(unlinked_file_append_to_existing_mods); 143 cov_mark::hit!(unlinked_file_append_to_existing_mods);
141 builder.insert(last.syntax().text_range().end(), format!("\n{}", mod_decl)); 144 let offset = last.syntax().text_range().end();
145 mod_decl_builder.insert(offset, format!("\n{}", mod_decl));
146 pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl));
142 } 147 }
143 None => { 148 None => {
144 // Prepend before the first item in the file. 149 // Prepend before the first item in the file.
145 match ast.items().next() { 150 match ast.items().next() {
146 Some(item) => { 151 Some(item) => {
147 cov_mark::hit!(unlinked_file_prepend_before_first_item); 152 cov_mark::hit!(unlinked_file_prepend_before_first_item);
148 builder.insert(item.syntax().text_range().start(), format!("{}\n\n", mod_decl)); 153 let offset = item.syntax().text_range().start();
154 mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl));
155 pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl));
149 } 156 }
150 None => { 157 None => {
151 // No items in the file, so just append at the end. 158 // No items in the file, so just append at the end.
152 cov_mark::hit!(unlinked_file_empty_file); 159 cov_mark::hit!(unlinked_file_empty_file);
153 builder.insert(ast.syntax().text_range().end(), format!("{}\n", mod_decl)); 160 let offset = ast.syntax().text_range().end();
161 mod_decl_builder.insert(offset, format!("{}\n", mod_decl));
162 pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl));
154 } 163 }
155 } 164 }
156 } 165 }
157 } 166 }
158 167
159 let edit = builder.finish();
160 let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); 168 let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
161 Some(fix( 169 Some(vec![
162 "add_mod_declaration", 170 fix(
163 &format!("Insert `{}`", mod_decl), 171 "add_mod_declaration",
164 SourceChange::from_text_edit(parent_file_id, edit), 172 &format!("Insert `{}`", mod_decl),
165 trigger_range, 173 SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()),
166 )) 174 trigger_range,
175 ),
176 fix(
177 "add_pub_mod_declaration",
178 &format!("Insert `{}`", pub_mod_decl),
179 SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()),
180 trigger_range,
181 ),
182 ])
167} 183}