1
1
use crate :: assist_context:: { AssistContext , Assists } ;
2
- use hir:: HirDisplay ;
2
+ use hir:: { HasVisibility , HirDisplay , Module } ;
3
3
use ide_db:: {
4
4
assists:: { AssistId , AssistKind } ,
5
- defs:: NameRefClass ,
5
+ base_db:: { FileId , Upcast } ,
6
+ defs:: { Definition , NameRefClass } ,
6
7
} ;
7
8
use syntax:: {
8
- ast:: { self , edit:: IndentLevel } ,
9
- AstNode ,
9
+ ast:: { self , edit:: IndentLevel , NameRef } ,
10
+ AstNode , Direction , SyntaxKind , TextSize ,
10
11
} ;
11
12
12
13
// Assist: generate_constant
@@ -32,13 +33,6 @@ use syntax::{
32
33
33
34
pub ( crate ) fn generate_constant ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
34
35
let constant_token = ctx. find_node_at_offset :: < ast:: NameRef > ( ) ?;
35
- let expr = constant_token. syntax ( ) . ancestors ( ) . find_map ( ast:: Expr :: cast) ?;
36
- let statement = expr. syntax ( ) . ancestors ( ) . find_map ( ast:: Stmt :: cast) ?;
37
- let ty = ctx. sema . type_of_expr ( & expr) ?;
38
- let scope = ctx. sema . scope ( statement. syntax ( ) ) ?;
39
- let module = scope. module ( ) ;
40
- let type_name = ty. original ( ) . display_source_code ( ctx. db ( ) , module. into ( ) ) . ok ( ) ?;
41
- let indent = IndentLevel :: from_node ( statement. syntax ( ) ) ;
42
36
if constant_token. to_string ( ) . chars ( ) . any ( |it| !( it. is_uppercase ( ) || it == '_' ) ) {
43
37
cov_mark:: hit!( not_constant_name) ;
44
38
return None ;
@@ -47,20 +41,104 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Optio
47
41
cov_mark:: hit!( already_defined) ;
48
42
return None ;
49
43
}
44
+ let expr = constant_token. syntax ( ) . ancestors ( ) . find_map ( ast:: Expr :: cast) ?;
45
+ let statement = expr. syntax ( ) . ancestors ( ) . find_map ( ast:: Stmt :: cast) ?;
46
+ let ty = ctx. sema . type_of_expr ( & expr) ?;
47
+ let scope = ctx. sema . scope ( statement. syntax ( ) ) ?;
48
+ let constant_module = scope. module ( ) ;
49
+ let type_name = ty. original ( ) . display_source_code ( ctx. db ( ) , constant_module. into ( ) ) . ok ( ) ?;
50
50
let target = statement. syntax ( ) . parent ( ) ?. text_range ( ) ;
51
+ let path = constant_token. syntax ( ) . ancestors ( ) . find_map ( ast:: Path :: cast) ?;
52
+
53
+ let name_refs = path. segments ( ) . map ( |s| s. name_ref ( ) ) ;
54
+ let mut outer_exists = false ;
55
+ let mut not_exist_name_ref = Vec :: new ( ) ;
56
+ let mut current_module = constant_module;
57
+ for name_ref in name_refs {
58
+ let name_ref_value = name_ref?;
59
+ let name_ref_class = NameRefClass :: classify ( & ctx. sema , & name_ref_value) ;
60
+ match name_ref_class {
61
+ Some ( NameRefClass :: Definition ( Definition :: Module ( m) ) ) => {
62
+ if !m. visibility ( ctx. sema . db ) . is_visible_from ( ctx. sema . db , constant_module. into ( ) ) {
63
+ return None ;
64
+ }
65
+ outer_exists = true ;
66
+ current_module = m;
67
+ }
68
+ Some ( _) => {
69
+ return None ;
70
+ }
71
+ None => {
72
+ not_exist_name_ref. push ( name_ref_value) ;
73
+ }
74
+ }
75
+ }
76
+ let ( offset, indent, file_id, post_string) =
77
+ target_data_for_generate_constant ( ctx, current_module, constant_module) . unwrap_or_else ( || {
78
+ let indent = IndentLevel :: from_node ( statement. syntax ( ) ) ;
79
+ ( statement. syntax ( ) . text_range ( ) . start ( ) , indent, None , format ! ( "\n {}" , indent) )
80
+ } ) ;
81
+
82
+ let text = get_text_for_generate_constant ( not_exist_name_ref, indent, outer_exists, type_name) ?;
51
83
acc. add (
52
84
AssistId ( "generate_constant" , AssistKind :: QuickFix ) ,
53
85
"Generate constant" ,
54
86
target,
55
87
|builder| {
56
- builder . insert (
57
- statement . syntax ( ) . text_range ( ) . start ( ) ,
58
- format ! ( "const {}: {} = $0; \n {}" , constant_token , type_name , indent ) ,
59
- ) ;
88
+ if file_id . is_some ( ) {
89
+ builder . edit_file ( file_id . unwrap ( ) ) ;
90
+ }
91
+ builder . insert ( offset , format ! ( "{}{}" , text , post_string ) ) ;
60
92
} ,
61
93
)
62
94
}
63
95
96
+ fn get_text_for_generate_constant (
97
+ mut not_exist_name_ref : Vec < NameRef > ,
98
+ indent : IndentLevel ,
99
+ outer_exists : bool ,
100
+ type_name : String ,
101
+ ) -> Option < String > {
102
+ let constant_token = not_exist_name_ref. pop ( ) ?;
103
+ let vis = if not_exist_name_ref. len ( ) == 0 && !outer_exists { "" } else { "\n pub " } ;
104
+ let mut text = format ! ( "{}const {}: {} = $0;" , vis, constant_token, type_name) ;
105
+ while let Some ( name_ref) = not_exist_name_ref. pop ( ) {
106
+ let vis = if not_exist_name_ref. len ( ) == 0 && !outer_exists { "" } else { "\n pub " } ;
107
+ text = text. replace ( "\n " , "\n " ) ;
108
+ text = format ! ( "{}mod {} {{{}\n }}" , vis, name_ref. to_string( ) , text) ;
109
+ }
110
+ Some ( text. replace ( "\n " , & format ! ( "\n {}" , indent) ) )
111
+ }
112
+
113
+ fn target_data_for_generate_constant (
114
+ ctx : & AssistContext ,
115
+ current_module : Module ,
116
+ constant_module : Module ,
117
+ ) -> Option < ( TextSize , IndentLevel , Option < FileId > , String ) > {
118
+ if current_module == constant_module {
119
+ // insert in current file
120
+ return None ;
121
+ }
122
+ let in_file_source = current_module. definition_source ( ctx. sema . db ) ;
123
+ let file_id = in_file_source. file_id . original_file ( ctx. sema . db . upcast ( ) ) ;
124
+ match in_file_source. value {
125
+ hir:: ModuleSource :: Module ( module_node) => {
126
+ let indent = IndentLevel :: from_node ( module_node. syntax ( ) ) ;
127
+ let l_curly_token = module_node. item_list ( ) ?. l_curly_token ( ) ?;
128
+ let offset = l_curly_token. text_range ( ) . end ( ) ;
129
+
130
+ let siblings_has_newline = l_curly_token
131
+ . siblings_with_tokens ( Direction :: Next )
132
+ . find ( |it| it. kind ( ) == SyntaxKind :: WHITESPACE && it. to_string ( ) . contains ( "\n " ) )
133
+ . is_some ( ) ;
134
+ let post_string =
135
+ if siblings_has_newline { format ! ( "{}" , indent) } else { format ! ( "\n {}" , indent) } ;
136
+ Some ( ( offset, indent + 1 , Some ( file_id) , post_string) )
137
+ }
138
+ _ => Some ( ( TextSize :: from ( 0 ) , 0 . into ( ) , Some ( file_id) , "\n " . into ( ) ) ) ,
139
+ }
140
+ }
141
+
64
142
#[ cfg( test) ]
65
143
mod tests {
66
144
use super :: * ;
@@ -113,6 +191,62 @@ impl S {
113
191
}
114
192
fn main() {
115
193
let v = S::new(capa$0city);
194
+ }"# ,
195
+ ) ;
196
+ }
197
+
198
+ #[ test]
199
+ fn test_constant_with_path ( ) {
200
+ check_assist (
201
+ generate_constant,
202
+ r#"mod foo {}
203
+ fn bar() -> i32 {
204
+ foo::A_CON$0STANT
205
+ }"# ,
206
+ r#"mod foo {
207
+ pub const A_CONSTANT: i32 = $0;
208
+ }
209
+ fn bar() -> i32 {
210
+ foo::A_CONSTANT
211
+ }"# ,
212
+ ) ;
213
+ }
214
+
215
+ #[ test]
216
+ fn test_constant_with_longer_path ( ) {
217
+ check_assist (
218
+ generate_constant,
219
+ r#"mod foo {
220
+ pub mod goo {}
221
+ }
222
+ fn bar() -> i32 {
223
+ foo::goo::A_CON$0STANT
224
+ }"# ,
225
+ r#"mod foo {
226
+ pub mod goo {
227
+ pub const A_CONSTANT: i32 = $0;
228
+ }
229
+ }
230
+ fn bar() -> i32 {
231
+ foo::goo::A_CONSTANT
232
+ }"# ,
233
+ ) ;
234
+ }
235
+
236
+ #[ test]
237
+ fn test_constant_with_not_exist_longer_path ( ) {
238
+ check_assist (
239
+ generate_constant,
240
+ r#"fn bar() -> i32 {
241
+ foo::goo::A_CON$0STANT
242
+ }"# ,
243
+ r#"mod foo {
244
+ pub mod goo {
245
+ pub const A_CONSTANT: i32 = $0;
246
+ }
247
+ }
248
+ fn bar() -> i32 {
249
+ foo::goo::A_CONSTANT
116
250
}"# ,
117
251
) ;
118
252
}
0 commit comments