11use syntax:: {
22 ast:: { self , AstNode } ,
3- TextRange , TextSize , T ,
3+ SyntaxElement , TextRange , TextSize , T ,
44} ;
55
66use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
@@ -22,62 +22,118 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
2222// ```
2323pub ( crate ) fn remove_dbg ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
2424 let macro_call = ctx. find_node_at_offset :: < ast:: MacroCall > ( ) ?;
25+ let new_contents = adjusted_macro_contents ( & macro_call) ?;
2526
26- if !is_valid_macrocall ( & macro_call, "dbg" ) ? {
27- return None ;
28- }
29-
30- let is_leaf = macro_call. syntax ( ) . next_sibling ( ) . is_none ( ) ;
31-
27+ let macro_text_range = macro_call. syntax ( ) . text_range ( ) ;
3228 let macro_end = if macro_call. semicolon_token ( ) . is_some ( ) {
33- macro_call . syntax ( ) . text_range ( ) . end ( ) - TextSize :: of ( ';' )
29+ macro_text_range . end ( ) - TextSize :: of ( ';' )
3430 } else {
35- macro_call . syntax ( ) . text_range ( ) . end ( )
31+ macro_text_range . end ( )
3632 } ;
3733
38- // macro_range determines what will be deleted and replaced with macro_content
39- let macro_range = TextRange :: new ( macro_call. syntax ( ) . text_range ( ) . start ( ) , macro_end) ;
40- let paste_instead_of_dbg = {
41- let text = macro_call. token_tree ( ) ?. syntax ( ) . text ( ) ;
42-
43- // leafiness determines if we should include the parenthesis or not
44- let slice_index: TextRange = if is_leaf {
45- // leaf means - we can extract the contents of the dbg! in text
46- TextRange :: new ( TextSize :: of ( '(' ) , text. len ( ) - TextSize :: of ( ')' ) )
47- } else {
48- // not leaf - means we should keep the parens
49- TextRange :: up_to ( text. len ( ) )
50- } ;
51- text. slice ( slice_index) . to_string ( )
52- } ;
34+ acc. add (
35+ AssistId ( "remove_dbg" , AssistKind :: Refactor ) ,
36+ "Remove dbg!()" ,
37+ macro_text_range,
38+ |builder| {
39+ builder. replace ( TextRange :: new ( macro_text_range. start ( ) , macro_end) , new_contents) ;
40+ } ,
41+ )
42+ }
5343
54- let target = macro_call. syntax ( ) . text_range ( ) ;
55- acc. add ( AssistId ( "remove_dbg" , AssistKind :: Refactor ) , "Remove dbg!()" , target, |builder| {
56- builder. replace ( macro_range, paste_instead_of_dbg) ;
57- } )
44+ fn adjusted_macro_contents ( macro_call : & ast:: MacroCall ) -> Option < String > {
45+ let contents = get_valid_macrocall_contents ( & macro_call, "dbg" ) ?;
46+ let is_leaf = macro_call. syntax ( ) . next_sibling ( ) . is_none ( ) ;
47+ let macro_text_with_brackets = macro_call. token_tree ( ) ?. syntax ( ) . text ( ) ;
48+ let slice_index = if is_leaf || !needs_parentheses_around_macro_contents ( contents) {
49+ TextRange :: new ( TextSize :: of ( '(' ) , macro_text_with_brackets. len ( ) - TextSize :: of ( ')' ) )
50+ } else {
51+ // leave parenthesis around macro contents to preserve the semantics
52+ TextRange :: up_to ( macro_text_with_brackets. len ( ) )
53+ } ;
54+ Some ( macro_text_with_brackets. slice ( slice_index) . to_string ( ) )
5855}
5956
6057/// Verifies that the given macro_call actually matches the given name
61- /// and contains proper ending tokens
62- fn is_valid_macrocall ( macro_call : & ast:: MacroCall , macro_name : & str ) -> Option < bool > {
58+ /// and contains proper ending tokens, then returns the contents between the ending tokens
59+ fn get_valid_macrocall_contents (
60+ macro_call : & ast:: MacroCall ,
61+ macro_name : & str ,
62+ ) -> Option < Vec < SyntaxElement > > {
6363 let path = macro_call. path ( ) ?;
6464 let name_ref = path. segment ( ) ?. name_ref ( ) ?;
6565
6666 // Make sure it is actually a dbg-macro call, dbg followed by !
6767 let excl = path. syntax ( ) . next_sibling_or_token ( ) ?;
68-
6968 if name_ref. text ( ) != macro_name || excl. kind ( ) != T ! [ !] {
7069 return None ;
7170 }
7271
73- let node = macro_call. token_tree ( ) ?. syntax ( ) . clone ( ) ;
74- let first_child = node. first_child_or_token ( ) ?;
75- let last_child = node. last_child_or_token ( ) ?;
72+ let mut children_with_tokens = macro_call. token_tree ( ) ?. syntax ( ) . children_with_tokens ( ) ;
73+ let first_child = children_with_tokens. next ( ) ?;
74+ let mut contents_between_brackets = children_with_tokens. collect :: < Vec < _ > > ( ) ;
75+ let last_child = contents_between_brackets. pop ( ) ?;
76+
77+ if contents_between_brackets. is_empty ( ) {
78+ None
79+ } else {
80+ match ( first_child. kind ( ) , last_child. kind ( ) ) {
81+ ( T ! [ '(' ] , T ! [ ')' ] ) | ( T ! [ '[' ] , T ! [ ']' ] ) | ( T ! [ '{' ] , T ! [ '}' ] ) => {
82+ Some ( contents_between_brackets)
83+ }
84+ _ => None ,
85+ }
86+ }
87+ }
88+
89+ fn needs_parentheses_around_macro_contents ( macro_contents : Vec < SyntaxElement > ) -> bool {
90+ if macro_contents. len ( ) < 2 {
91+ return false ;
92+ }
93+
94+ let mut macro_contents_kind_not_in_brackets = Vec :: with_capacity ( macro_contents. len ( ) ) ;
7695
77- match ( first_child. kind ( ) , last_child. kind ( ) ) {
78- ( T ! [ '(' ] , T ! [ ')' ] ) | ( T ! [ '[' ] , T ! [ ']' ] ) | ( T ! [ '{' ] , T ! [ '}' ] ) => Some ( true ) ,
79- _ => Some ( false ) ,
96+ let mut first_bracket_in_macro = None ;
97+ let mut unpaired_brackets_in_contents = Vec :: new ( ) ;
98+ for element in macro_contents {
99+ match element. kind ( ) {
100+ T ! [ '(' ] | T ! [ '[' ] | T ! [ '{' ] => {
101+ if let None = first_bracket_in_macro {
102+ first_bracket_in_macro = Some ( element. clone ( ) )
103+ }
104+ unpaired_brackets_in_contents. push ( element) ;
105+ }
106+ T ! [ ')' ] => {
107+ if !matches ! ( unpaired_brackets_in_contents. pop( ) , Some ( correct_bracket) if correct_bracket. kind( ) == T ![ '(' ] )
108+ {
109+ return true ;
110+ }
111+ }
112+ T ! [ ']' ] => {
113+ if !matches ! ( unpaired_brackets_in_contents. pop( ) , Some ( correct_bracket) if correct_bracket. kind( ) == T ![ '[' ] )
114+ {
115+ return true ;
116+ }
117+ }
118+ T ! [ '}' ] => {
119+ if !matches ! ( unpaired_brackets_in_contents. pop( ) , Some ( correct_bracket) if correct_bracket. kind( ) == T ![ '{' ] )
120+ {
121+ return true ;
122+ }
123+ }
124+ other_kind => {
125+ if unpaired_brackets_in_contents. is_empty ( ) {
126+ macro_contents_kind_not_in_brackets. push ( other_kind) ;
127+ }
128+ }
129+ }
80130 }
131+
132+ !unpaired_brackets_in_contents. is_empty ( )
133+ || matches ! ( first_bracket_in_macro, Some ( bracket) if bracket. kind( ) != T ![ '(' ] )
134+ || macro_contents_kind_not_in_brackets
135+ . into_iter ( )
136+ . any ( |macro_contents_kind| macro_contents_kind. is_punct ( ) )
81137}
82138
83139#[ cfg( test) ]
@@ -156,13 +212,38 @@ fn foo(n: usize) {
156212 ) ;
157213 }
158214
215+ #[ test]
216+ fn remove_dbg_from_non_leaf_simple_expression ( ) {
217+ check_assist (
218+ remove_dbg,
219+ "
220+ fn main() {
221+ let mut a = 1;
222+ while dbg!<|>(a) < 10000 {
223+ a += 1;
224+ }
225+ }
226+ " ,
227+ "
228+ fn main() {
229+ let mut a = 1;
230+ while a < 10000 {
231+ a += 1;
232+ }
233+ }
234+ " ,
235+ ) ;
236+ }
237+
159238 #[ test]
160239 fn test_remove_dbg_keep_expression ( ) {
161240 check_assist (
162241 remove_dbg,
163242 r#"let res = <|>dbg!(a + b).foo();"# ,
164243 r#"let res = (a + b).foo();"# ,
165244 ) ;
245+
246+ check_assist ( remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"# , r#"let res = (2 + 2) * 5"# ) ;
166247 }
167248
168249 #[ test]
0 commit comments