[groovy] allow to select 'this' reference for 'introduce X' refactoring
[idea/community.git] / plugins / groovy / test / org / jetbrains / plugins / groovy / refactoring / introduce / field / GrIntroduceFieldTest.groovy
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.plugins.groovy.refactoring.introduce.field
17
18 import com.intellij.openapi.application.WriteAction
19 import com.intellij.psi.JavaPsiFacade
20 import com.intellij.psi.PsiType
21 import com.intellij.psi.impl.source.PostprocessReformattingAspect
22 import com.intellij.refactoring.introduce.inplace.OccurrencesChooser.ReplaceChoice
23 import org.jetbrains.annotations.NotNull
24 import org.jetbrains.annotations.Nullable
25 import org.jetbrains.plugins.groovy.GroovyFileType
26 import org.jetbrains.plugins.groovy.LightGroovyTestCase
27 import org.jetbrains.plugins.groovy.refactoring.introduce.IntroduceConstantTest
28 import org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldSettings.Init
29 import org.jetbrains.plugins.groovy.util.TestUtils
30
31 import static com.intellij.refactoring.introduce.inplace.OccurrencesChooser.ReplaceChoice.ALL
32 import static org.jetbrains.plugins.groovy.refactoring.introduce.field.GrIntroduceFieldSettings.Init.*
33
34 /**
35  * @author Maxim.Medvedev
36  */
37 class GrIntroduceFieldTest extends LightGroovyTestCase {
38   @Override
39   protected String getBasePath() {
40     "${TestUtils.testDataPath}refactoring/introduceField/"
41   }
42
43   void testSimple() {
44     doTest(false, false, false, CUR_METHOD, false, null)
45   }
46
47   void testDeclareFinal() {
48     doTest(false, false, true, FIELD_DECLARATION, false, null)
49   }
50
51   void testCreateConstructor() {
52     doTest(false, false, true, CONSTRUCTOR, true, null)
53   }
54
55   void testManyConstructors() {
56     doTest(false, false, true, CONSTRUCTOR, true, null)
57   }
58
59   void testDontReplaceStaticOccurrences() {
60     doTest(false, false, true, FIELD_DECLARATION, true, null)
61   }
62
63   void testQualifyUsages() {
64     doTest(false, false, true, FIELD_DECLARATION, true, null)
65   }
66
67   void testReplaceLocalVar() {
68     doTest(false, true, false, CUR_METHOD, true, null)
69   }
70
71   void testIntroduceLocalVarByDeclaration() {
72     doTest(false, true, false, FIELD_DECLARATION, true, null)
73   }
74
75   void testReplaceExpressionWithAssignment() {
76     doTest(false, false, false, CUR_METHOD, false, null)
77   }
78
79   void testAnonymousClass() {
80     doTest(false, false, false, CUR_METHOD, false, null)
81   }
82
83   void testAnonymous2() {
84     doTest(false, false, false, CONSTRUCTOR, false, null)
85   }
86
87   void testAnonymous3() {
88     doTest(false, false, false, CONSTRUCTOR, false, null)
89   }
90
91   void testInitializeInCurrentMethod() {
92     doTest(false, true, true, CUR_METHOD, false, null)
93   }
94
95   void testScriptBody() {
96     addGroovyTransformField()
97     doTest('''\
98 print <selection>'abc'</selection>
99 ''', '''\
100 import groovy.transform.Field
101
102 @Field f = 'abc'
103 print f<caret>
104 ''', false, false, false, FIELD_DECLARATION)
105   }
106
107   void testScriptMethod() {
108     addGroovyTransformField()
109     doTest('''\
110 def foo() {
111   print <selection>'abc'</selection>
112 }
113 ''', '''\
114 import groovy.transform.Field
115
116 @Field final f = 'abc'
117
118 def foo() {
119   print f<caret>
120 }
121 ''', false, false, true, FIELD_DECLARATION)
122   }
123
124   void testStaticScriptMethod() {
125     addGroovyTransformField()
126     doTest('''\
127 static def foo() {
128   print <selection>'abc'</selection>
129 }
130 ''', '''\
131 import groovy.transform.Field
132
133 @Field static f = 'abc'
134
135 static def foo() {
136   print f<caret>
137 }
138 ''', true, false, false, FIELD_DECLARATION)
139   }
140
141   void testScriptMethod2() {
142     addGroovyTransformField()
143     doTest('''\
144 def foo() {
145   print <selection>'abc'</selection>
146 }
147 ''', '''\
148 import groovy.transform.Field
149
150 @Field f
151
152 def foo() {
153     f = 'abc'
154     print f<caret>
155 }
156 ''', false, false, false, CUR_METHOD)
157   }
158
159   void testSetUp1() throws Exception {
160     addTestCase()
161     doTest('''\
162 class MyTest extends GroovyTestCase {
163     void foo() {
164         print <selection>'ac'</selection>
165     }
166 }
167 ''', '''\
168 class MyTest extends GroovyTestCase {
169     def f
170
171     void foo() {
172         print f<caret>
173     }
174
175     void setUp() {
176         super.setUp()
177         f = 'ac'
178     }
179 }
180 ''',
181 false, false, false, SETUP_METHOD)
182   }
183
184   void testSetUp2() throws Exception {
185     addTestCase()
186
187     doTest('''\
188 class MyTest extends GroovyTestCase {
189     void setUp() {
190         super.setUp()
191         def x = 'abc'
192     }
193
194     void foo() {
195         print <selection>'ac'</selection>
196     }
197 }
198 ''', '''\
199 class MyTest extends GroovyTestCase {
200     def f
201
202     void setUp() {
203         super.setUp()
204         def x = 'abc'
205     f = 'ac'
206     }
207
208     void foo() {
209         print f<caret>
210     }
211 }
212 ''',
213            false, false, false, SETUP_METHOD)
214   }
215
216   void testStringPart0() {
217     doTest('''\
218 class A {
219     def foo() {
220         print 'a<selection>b</selection>c'
221     }
222 }''', '''\
223 class A {
224     def f = 'b'
225
226     def foo() {
227         print 'a' + f<caret> + 'c'
228     }
229 }''', false, false, false, FIELD_DECLARATION, false, null)
230   }
231
232   void testStringPart1() {
233     doTest('''\
234 class A {
235     def foo() {
236         print 'a<selection>b</selection>c'
237     }
238 }''', '''\
239 class A {
240     def f
241
242     def foo() {
243         f = 'b'
244         print 'a' + f<caret> + 'c'
245     }
246 }''', false, false, false, CUR_METHOD, false, null)
247   }
248
249   void testStringPart2() {
250     doTest('''\
251 class A {
252     def foo() {
253         def f = 5
254         print 'a<selection>b</selection>c'
255     }
256 }''', '''\
257 class A {
258     def f
259
260     def foo() {
261         def f = 5
262         this.f = 'b'
263         print 'a' + this.f<caret> + 'c'
264     }
265 }''', false, false, false, CUR_METHOD, false, null)
266   }
267
268   void testGStringInjection() {
269     doTest('''\
270 class GroovyLightProjectDescriptor  {
271     public void configureModule() {
272         print ("$<selection>mockGroovy2_1LibraryName</selection>!/");
273     }
274
275     def getMockGroovy2_1LibraryName() {''}
276 }
277 ''', '''\
278 class GroovyLightProjectDescriptor  {
279     def f
280
281     public void configureModule() {
282         f = mockGroovy2_1LibraryName
283         print ("${f}!/");
284     }
285
286     def getMockGroovy2_1LibraryName() {''}
287 }
288 ''', false, false, false, CUR_METHOD)
289   }
290
291   void testGStringInjection2() {
292     doTest('''\
293 class GroovyLightProjectDescriptor  {
294     public void configureModule() {
295         print ("$<selection>mockGroovy2_1LibraryName</selection>.bytes!/");
296     }
297
298     def getMockGroovy2_1LibraryName() {''}
299 }
300 ''', '''\
301 class GroovyLightProjectDescriptor  {
302     def f
303
304     public void configureModule() {
305         f = mockGroovy2_1LibraryName
306         print ("${f.bytes}!/");
307     }
308
309     def getMockGroovy2_1LibraryName() {''}
310 }
311 ''', false, false, false, CUR_METHOD)
312   }
313
314   void 'test GString closure injection and initialize in current method'() {
315     doTest '''\
316 class GroovyLightProjectDescriptor  {
317     public void configureModule() {
318         print ("${<selection>mockGroovy2_1LibraryName</selection>}!/");
319     }
320
321     def getMockGroovy2_1LibraryName() {''}
322 }
323 ''', '''\
324 class GroovyLightProjectDescriptor  {
325     def f
326
327     public void configureModule() {
328         f = mockGroovy2_1LibraryName
329         print ("${f}!/");
330     }
331
332     def getMockGroovy2_1LibraryName() {''}
333 }
334 ''', false, false, false, CUR_METHOD
335   }
336
337   void testInitializeInMethodInThenBranch() {
338     doTest('''\
339 class A {
340     def foo() {
341         if (abc) print <selection>2</selection>
342     }
343 }
344 ''', '''\
345 class A {
346     def f
347
348     def foo() {
349         if (abc) {
350             f = 2
351             print f
352         }
353     }
354 }
355 ''', false, false, false, CUR_METHOD, false, null)
356   }
357
358   void testFromVar() {
359     doTest('''\
360 class A {
361     def foo() {
362         def <selection>a = 5</selection>
363         print a
364     }
365 }''', '''\
366 class A {
367     def f = 5
368
369     def foo() {
370         print f
371     }
372 }''', false, true, false, FIELD_DECLARATION, true, null)
373   }
374
375   void 'test replace top level expression within constructor and initialize in current method'() {
376     doTest '''\
377 class TestClass {
378     TestClass() {
379         new St<caret>ring()
380     }
381
382     TestClass(a) {
383     }
384 }
385 ''', '''\
386 class TestClass {
387     def f
388
389     TestClass() {
390         f = new String()
391     }
392
393     TestClass(a) {
394     }
395 }
396 ''', false, false, false, CUR_METHOD
397   }
398
399   void 'test replace top level expression within constructor and initialize field'() {
400     doTest '''\
401 class TestClass {
402     TestClass() {
403         new St<caret>ring()
404     }
405
406     TestClass(a) {
407     }
408 }
409 ''', '''\
410 class TestClass {
411     def f = new String()
412
413     TestClass() {
414         f
415     }
416
417     TestClass(a) {
418     }
419 }
420 ''', false, false, false, FIELD_DECLARATION
421   }
422
423   void 'test replace top level expression within constructor and initialize in constructor'() {
424     doTest '''\
425 class TestClass {
426     TestClass() {
427         new St<caret>ring()
428     }
429
430     TestClass(a) {
431     }
432 }
433 ''', '''\
434 class TestClass {
435     def f
436
437     TestClass() {
438         f = new String()
439     }
440
441     TestClass(a) {
442         f = new String()
443     }
444 }
445 ''', false, false, false, CONSTRUCTOR
446   }
447
448   void 'test replace non top level expression within constructor and initialize in current method'() {
449     doTest '''\
450 class TestClass {
451     TestClass() {
452         <selection>new String()</selection>.empty
453     }
454
455     TestClass(a) {
456     }
457 }
458 ''', '''\
459 class TestClass {
460     def f
461
462     TestClass() {
463         f = new String()
464         f.empty
465     }
466
467     TestClass(a) {
468     }
469 }
470 ''', false, false, false, CUR_METHOD
471   }
472
473   void 'test replace non top level expression within constructor and initialize field'() {
474     doTest '''\
475 class TestClass {
476     TestClass() {
477         <selection>new String()</selection>.empty
478     }
479
480     TestClass(a) {
481     }
482 }
483 ''', '''\
484 class TestClass {
485     def f = new String()
486
487     TestClass() {
488         f.empty
489     }
490
491     TestClass(a) {
492     }
493 }
494 ''', false, false, false, FIELD_DECLARATION
495   }
496
497   void 'test replace non top level expression within constructor and initialize in constructor'() {
498     doTest '''\
499 class TestClass {
500     TestClass() {
501         <selection>new String()</selection>.empty
502     }
503
504     TestClass(a) {
505     }
506 }
507 ''', '''\
508 class TestClass {
509     def f
510
511     TestClass() {
512         f = new String()
513         f.empty
514     }
515
516     TestClass(a) {
517         f = new String()
518     }
519 }
520 ''', false, false, false, CONSTRUCTOR
521   }
522
523   void 'test replace string injection and initialize in constructor'() {
524     doTest '''\
525 class TestClass {
526     TestClass() {
527         "${<selection>new String()</selection>}"
528     }
529     TestClass(a) {
530     }
531 }
532 ''','''\
533 class TestClass {
534     def f
535
536     TestClass() {
537         f = new String()
538         "${f}"
539     }
540     TestClass(a) {
541         f = new String()
542     }
543 }
544 ''', false, false, false, CONSTRUCTOR
545   }
546
547   void 'test introduce field in script with invalid class name'() {
548     myFixture.configureByText "abcd-efgh.groovy", '''\
549 def aaa = "foo"
550 def bbb = "bar"
551 println(<selection>aaa + bbb</selection>)
552 '''
553     performRefactoring(null, false, false, false, CUR_METHOD, false)
554     myFixture.checkResult '''\
555 import groovy.transform.Field
556
557 @Field f
558 def aaa = "foo"
559 def bbb = "bar"
560 f = aaa + bbb
561 println(f)
562 '''
563   }
564
565   void 'test cannot initialize in current method when introducing from field initializer'() {
566     doTestInitInTarget '''
567 class A {
568   def object = <selection>new Object()</selection>
569 }
570 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION)
571
572     doTestInitInTarget '''
573 class A {
574   def object = <selection>new Object()</selection>
575   def object2 = new Object()
576 }
577 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION)
578
579     doTestInitInTarget '''
580 class A {
581   def object = <selection>new Object()</selection>
582   def object2 = new Object()
583 }
584 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION), ReplaceChoice.NO
585   }
586
587   void 'test can not initialize in current method with some occurence outside'() {
588     doTestInitInTarget '''
589 class A {
590   def field = new Object()
591   def foo() {
592     def a = <selection>new Object()</selection>
593   }
594 }
595 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION)
596   }
597
598   void 'test can initialize in current method from within method'() {
599     doTestInitInTarget '''
600 class A {
601   def foo() {
602     def a = <selection>new Object()</selection>
603   }
604 }
605 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION, CUR_METHOD)
606
607     doTestInitInTarget '''
608 class A {
609   def field = new Object()
610   def foo() {
611     def a = <selection>new Object()</selection>
612   }
613 }
614 ''', EnumSet.of(CONSTRUCTOR, FIELD_DECLARATION, CUR_METHOD), ReplaceChoice.NO
615   }
616
617   void 'test can initialize script field in current method only'() {
618     doTestInitInTarget '''
619 def a = 1
620 def b = 2
621 println(<selection>a + b</selection>)
622 ''', EnumSet.of(CUR_METHOD)
623
624     doTestInitInTarget '''
625 def a = 1
626 def b = 2
627 println(<selection>a + b</selection>)
628 ''', EnumSet.of(CUR_METHOD), ReplaceChoice.NO
629
630     doTestInitInTarget '''
631 def a = 1
632 def b = 2
633 def c = a + b
634 println(<selection>a + b</selection>)
635 ''', EnumSet.of(CUR_METHOD)
636
637     doTestInitInTarget '''
638 def a = 1
639 def b = 2
640 def c = a + b
641 println(<selection>a + b</selection>)
642 ''', EnumSet.of(CUR_METHOD), ReplaceChoice.NO
643   }
644
645   void 'test introduce field from this'() {
646     doTest '''\
647 class A {
648     def bar 
649     def foo() {
650         th<caret>is.bar
651     }
652 }
653 ''', '''\
654 class A {
655     def bar
656     def f = this
657
658     def foo() {
659         f.bar
660     }
661 }
662 ''', false, false, false, FIELD_DECLARATION
663   }
664
665   private void doTest(final boolean isStatic,
666                       final boolean removeLocal,
667                       final boolean declareFinal,
668                       @NotNull final GrIntroduceFieldSettings.Init initIn,
669                       final boolean replaceAll = false,
670                       @Nullable final String selectedType = null) {
671     myFixture.configureByFile("${getTestName(false)}.groovy")
672     performRefactoring(selectedType, isStatic, removeLocal, declareFinal, initIn, replaceAll)
673     myFixture.checkResultByFile("${getTestName(false)}_after.groovy")
674   }
675
676   private void doTest(@NotNull final String textBefore,
677                       @NotNull String textAfter,
678                       final boolean isStatic,
679                       final boolean removeLocal,
680                       final boolean declareFinal,
681                       @NotNull final GrIntroduceFieldSettings.Init initIn,
682                       final boolean replaceAll = false,
683                       @Nullable final String selectedType = null) {
684     myFixture.configureByText("_.groovy", textBefore)
685     performRefactoring(selectedType, isStatic, removeLocal, declareFinal, initIn, replaceAll)
686     myFixture.checkResult(textAfter)
687   }
688
689   private void performRefactoring(String selectedType, boolean isStatic, boolean removeLocal, boolean declareFinal, GrIntroduceFieldSettings.Init initIn, boolean replaceAll) {
690     final PsiType type = selectedType == null ? null : JavaPsiFacade.getElementFactory(project).createTypeFromText(selectedType, myFixture.file)
691     WriteAction.run {
692       final IntroduceFieldTestHandler handler = new IntroduceFieldTestHandler(isStatic, removeLocal, declareFinal, initIn, replaceAll, type)
693       handler.invoke(project, myFixture.editor, myFixture.file, null)
694       PostprocessReformattingAspect.getInstance(project).doPostponedFormatting()
695     }
696   }
697
698   private void doTestInitInTarget(String text, EnumSet<Init> expected = EnumSet.noneOf(Init), ReplaceChoice replaceChoice = ALL) {
699     myFixture.configureByText(GroovyFileType.GROOVY_FILE_TYPE, text)
700     def handler = new GrIntroduceFieldHandler()
701
702     def expression = IntroduceConstantTest.findExpression(myFixture)
703     def variable = IntroduceConstantTest.findVariable(myFixture)
704     def stringPart = IntroduceConstantTest.findStringPart(myFixture)
705     def scopes = handler.findPossibleScopes(expression, variable, stringPart, editor)
706     assert scopes.length == 1
707     def scope = scopes[0]
708
709     def context = handler.getContext(getProject(), myFixture.editor, expression, variable, stringPart, scope)
710     def initPlaces = GrInplaceFieldIntroducer.getApplicableInitPlaces(context, replaceChoice == ALL)
711     assert initPlaces == expected
712   }
713 }