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,106 @@ 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
+ || {
79
+ let indent = IndentLevel :: from_node ( statement. syntax ( ) ) ;
80
+ ( statement. syntax ( ) . text_range ( ) . start ( ) , indent, None , format ! ( "\n {}" , indent) )
81
+ } ,
82
+ ) ;
83
+
84
+ let text = get_text_for_generate_constant ( not_exist_name_ref, indent, outer_exists, type_name) ?;
51
85
acc. add (
52
86
AssistId ( "generate_constant" , AssistKind :: QuickFix ) ,
53
87
"Generate constant" ,
54
88
target,
55
89
|builder| {
56
- builder . insert (
57
- statement . syntax ( ) . text_range ( ) . start ( ) ,
58
- format ! ( "const {}: {} = $0; \n {}" , constant_token , type_name , indent ) ,
59
- ) ;
90
+ if let Some ( file_id ) = file_id {
91
+ builder . edit_file ( file_id ) ;
92
+ }
93
+ builder . insert ( offset , format ! ( "{}{}" , text , post_string ) ) ;
60
94
} ,
61
95
)
62
96
}
63
97
98
+ fn get_text_for_generate_constant (
99
+ mut not_exist_name_ref : Vec < NameRef > ,
100
+ indent : IndentLevel ,
101
+ outer_exists : bool ,
102
+ type_name : String ,
103
+ ) -> Option < String > {
104
+ let constant_token = not_exist_name_ref. pop ( ) ?;
105
+ let vis = if not_exist_name_ref. len ( ) == 0 && !outer_exists { "" } else { "\n pub " } ;
106
+ let mut text = format ! ( "{}const {}: {} = $0;" , vis, constant_token, type_name) ;
107
+ while let Some ( name_ref) = not_exist_name_ref. pop ( ) {
108
+ let vis = if not_exist_name_ref. len ( ) == 0 && !outer_exists { "" } else { "\n pub " } ;
109
+ text = text. replace ( "\n " , "\n " ) ;
110
+ text = format ! ( "{}mod {} {{{}\n }}" , vis, name_ref. to_string( ) , text) ;
111
+ }
112
+ Some ( text. replace ( "\n " , & format ! ( "\n {}" , indent) ) )
113
+ }
114
+
115
+ fn target_data_for_generate_constant (
116
+ ctx : & AssistContext ,
117
+ current_module : Module ,
118
+ constant_module : Module ,
119
+ ) -> Option < ( TextSize , IndentLevel , Option < FileId > , String ) > {
120
+ if current_module == constant_module {
121
+ // insert in current file
122
+ return None ;
123
+ }
124
+ let in_file_source = current_module. definition_source ( ctx. sema . db ) ;
125
+ let file_id = in_file_source. file_id . original_file ( ctx. sema . db . upcast ( ) ) ;
126
+ match in_file_source. value {
127
+ hir:: ModuleSource :: Module ( module_node) => {
128
+ let indent = IndentLevel :: from_node ( module_node. syntax ( ) ) ;
129
+ let l_curly_token = module_node. item_list ( ) ?. l_curly_token ( ) ?;
130
+ let offset = l_curly_token. text_range ( ) . end ( ) ;
131
+
132
+ let siblings_has_newline = l_curly_token
133
+ . siblings_with_tokens ( Direction :: Next )
134
+ . find ( |it| it. kind ( ) == SyntaxKind :: WHITESPACE && it. to_string ( ) . contains ( "\n " ) )
135
+ . is_some ( ) ;
136
+ let post_string =
137
+ if siblings_has_newline { format ! ( "{}" , indent) } else { format ! ( "\n {}" , indent) } ;
138
+ Some ( ( offset, indent + 1 , Some ( file_id) , post_string) )
139
+ }
140
+ _ => Some ( ( TextSize :: from ( 0 ) , 0 . into ( ) , Some ( file_id) , "\n " . into ( ) ) ) ,
141
+ }
142
+ }
143
+
64
144
#[ cfg( test) ]
65
145
mod tests {
66
146
use super :: * ;
@@ -113,6 +193,62 @@ impl S {
113
193
}
114
194
fn main() {
115
195
let v = S::new(capa$0city);
196
+ }"# ,
197
+ ) ;
198
+ }
199
+
200
+ #[ test]
201
+ fn test_constant_with_path ( ) {
202
+ check_assist (
203
+ generate_constant,
204
+ r#"mod foo {}
205
+ fn bar() -> i32 {
206
+ foo::A_CON$0STANT
207
+ }"# ,
208
+ r#"mod foo {
209
+ pub const A_CONSTANT: i32 = $0;
210
+ }
211
+ fn bar() -> i32 {
212
+ foo::A_CONSTANT
213
+ }"# ,
214
+ ) ;
215
+ }
216
+
217
+ #[ test]
218
+ fn test_constant_with_longer_path ( ) {
219
+ check_assist (
220
+ generate_constant,
221
+ r#"mod foo {
222
+ pub mod goo {}
223
+ }
224
+ fn bar() -> i32 {
225
+ foo::goo::A_CON$0STANT
226
+ }"# ,
227
+ r#"mod foo {
228
+ pub mod goo {
229
+ pub const A_CONSTANT: i32 = $0;
230
+ }
231
+ }
232
+ fn bar() -> i32 {
233
+ foo::goo::A_CONSTANT
234
+ }"# ,
235
+ ) ;
236
+ }
237
+
238
+ #[ test]
239
+ fn test_constant_with_not_exist_longer_path ( ) {
240
+ check_assist (
241
+ generate_constant,
242
+ r#"fn bar() -> i32 {
243
+ foo::goo::A_CON$0STANT
244
+ }"# ,
245
+ r#"mod foo {
246
+ pub mod goo {
247
+ pub const A_CONSTANT: i32 = $0;
248
+ }
249
+ }
250
+ fn bar() -> i32 {
251
+ foo::goo::A_CONSTANT
116
252
}"# ,
117
253
) ;
118
254
}
0 commit comments