11use crate :: assist_context:: { AssistContext , Assists } ;
2- use hir:: HirDisplay ;
2+ use hir:: { HasVisibility , HirDisplay , Module } ;
33use ide_db:: {
44 assists:: { AssistId , AssistKind } ,
5- defs:: NameRefClass ,
5+ base_db:: { FileId , Upcast } ,
6+ defs:: { Definition , NameRefClass } ,
67} ;
78use syntax:: {
8- ast:: { self , edit:: IndentLevel } ,
9- AstNode ,
9+ ast:: { self , edit:: IndentLevel , NameRef } ,
10+ AstNode , Direction , SyntaxKind , TextSize ,
1011} ;
1112
1213// Assist: generate_constant
@@ -32,13 +33,6 @@ use syntax::{
3233
3334pub ( crate ) fn generate_constant ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
3435 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 ( ) ) ;
4236 if constant_token. to_string ( ) . chars ( ) . any ( |it| !( it. is_uppercase ( ) || it == '_' ) ) {
4337 cov_mark:: hit!( not_constant_name) ;
4438 return None ;
@@ -47,20 +41,106 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Optio
4741 cov_mark:: hit!( already_defined) ;
4842 return None ;
4943 }
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 ( ) ?;
5050 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) ?;
5185 acc. add (
5286 AssistId ( "generate_constant" , AssistKind :: QuickFix ) ,
5387 "Generate constant" ,
5488 target,
5589 |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 ) ) ;
6094 } ,
6195 )
6296}
6397
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+
64144#[ cfg( test) ]
65145mod tests {
66146 use super :: * ;
@@ -113,6 +193,62 @@ impl S {
113193}
114194fn main() {
115195 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
116252}"# ,
117253 ) ;
118254 }
0 commit comments