15
15
*/
16
16
package org .openrewrite .java ;
17
17
18
- import lombok .AccessLevel ;
19
- import lombok .Getter ;
18
+ import lombok .EqualsAndHashCode ;
20
19
import lombok .Value ;
21
20
import org .jspecify .annotations .Nullable ;
22
- import org .openrewrite .Cursor ;
23
- import org .openrewrite .ExecutionContext ;
24
- import org .openrewrite .Recipe ;
25
- import org .openrewrite .TreeVisitor ;
21
+ import org .openrewrite .*;
22
+ import org .openrewrite .java .search .UsesMethod ;
26
23
import org .openrewrite .java .tree .*;
27
24
28
25
import java .util .*;
32
29
import static java .lang .String .format ;
33
30
import static java .util .Collections .emptySet ;
34
31
import static java .util .Objects .requireNonNull ;
35
- import static java .util .stream .Collectors .toMap ;
36
- import static java .util .stream .Collectors .toSet ;
37
32
33
+ @ Incubating (since = "8.63.0" )
34
+ @ EqualsAndHashCode (callSuper = false )
35
+ @ Value
38
36
public class InlineMethodCalls extends Recipe {
37
+ private static final Pattern TEMPLATE_IDENTIFIER = Pattern .compile ("#\\ {(\\ p{javaJavaIdentifierStart}\\ p{javaJavaIdentifierPart}*):any\\ (.*?\\ )}" );
39
38
40
- private static final String INLINE_ME = "InlineMe" ;
39
+ @ Option (displayName = "Method pattern" ,
40
+ description = "A method pattern that is used to find matching method invocations." ,
41
+ example = "com.google.common.base.Preconditions checkNotNull(..)" )
42
+ String methodPattern ;
43
+
44
+ @ Option (displayName = "Replacement template" ,
45
+ description = "The replacement template for the method invocation. Parameters can be referenced using their names from the original method." ,
46
+ example = "java.util.Objects.requireNonNull(#{p0})" )
47
+ String replacement ;
48
+
49
+ @ Option (displayName = "Imports" ,
50
+ description = "List of regular imports to add when the replacement is made." ,
51
+ required = false ,
52
+ example = "[\" java.util.Objects\" ]" )
53
+ @ Nullable
54
+ Set <String > imports ;
55
+
56
+ @ Option (displayName = "Static imports" ,
57
+ description = "List of static imports to add when the replacement is made." ,
58
+ required = false ,
59
+ example = "[\" java.util.Collections.emptyList\" ]" )
60
+ @ Nullable
61
+ Set <String > staticImports ;
62
+
63
+ @ Option (displayName = "Classpath from resources" ,
64
+ description = "List of paths to JAR files on the classpath for parsing the replacement template." ,
65
+ required = false ,
66
+ example = "[\" guava-33.4.8-jre\" ]" )
67
+ @ Nullable
68
+ Set <String > classpathFromResources ;
41
69
42
70
@ Override
43
71
public String getDisplayName () {
44
- return "Inline methods annotated with `@InlineMe` " ;
72
+ return "Inline method calls " ;
45
73
}
46
74
47
75
@ Override
48
76
public String getDescription () {
49
- return "Apply inlinings as defined by Error Prone's [`@InlineMe` annotation](https://errorprone.info/docs/inlineme), " +
50
- "or compatible annotations. Uses the template and method arguments to replace method calls. " +
51
- "Supports both methods invocations and constructor calls, with optional new imports." ;
77
+ return "Inline method calls using a template replacement pattern. " +
78
+ "Supports both method invocations and constructor calls, with optional imports." ;
52
79
}
53
80
54
81
@ Override
55
82
public TreeVisitor <?, ExecutionContext > getVisitor () {
56
- // XXX Preconditions can not yet pick up the `@InlineMe` annotation on methods used
57
- return new JavaVisitor <ExecutionContext >() {
83
+ MethodMatcher matcher = new MethodMatcher ( methodPattern , true );
84
+ return Preconditions . check ( new UsesMethod <>( methodPattern ), new JavaVisitor <ExecutionContext >() {
58
85
@ Override
59
86
public J visitMethodInvocation (J .MethodInvocation method , ExecutionContext ctx ) {
60
- J .MethodInvocation mi = (J .MethodInvocation ) super .visitMethodInvocation (method , ctx );
61
- InlineMeValues values = findInlineMeValues (mi .getMethodType ());
62
- if (values == null ) {
63
- return mi ;
87
+ if (matcher .matches (method )) {
88
+ return replaceMethodCall (method , ctx );
64
89
}
65
- Template template = values .template (mi );
66
- if (template == null ) {
67
- return mi ;
68
- }
69
- removeAndAddImports (method , values .getImports (), values .getStaticImports ());
70
- J replacement = JavaTemplate .builder (template .getString ())
71
- .contextSensitive ()
72
- .imports (values .getImports ().toArray (new String [0 ]))
73
- .staticImports (values .getStaticImports ().toArray (new String [0 ]))
74
- .javaParser (JavaParser .fromJavaVersion ().classpath (JavaParser .runtimeClasspath ()))
75
- .build ()
76
- .apply (updateCursor (mi ), mi .getCoordinates ().replace (), template .getParameters ());
77
- return avoidMethodSelfReferences (mi , replacement );
90
+ return super .visitMethodInvocation (method , ctx );
78
91
}
79
92
80
93
@ Override
81
94
public J visitNewClass (J .NewClass newClass , ExecutionContext ctx ) {
82
- J .NewClass nc = (J .NewClass ) super .visitNewClass (newClass , ctx );
83
- InlineMeValues values = findInlineMeValues (nc .getConstructorType ());
84
- if (values == null ) {
85
- return nc ;
86
- }
87
- Template template = values .template (nc );
88
- if (template == null ) {
89
- return nc ;
95
+ if (matcher .matches (newClass )) {
96
+ return replaceMethodCall (newClass , ctx );
90
97
}
91
- removeAndAddImports (newClass , values .getImports (), values .getStaticImports ());
92
- J replacement = JavaTemplate .builder (template .getString ())
93
- .contextSensitive ()
94
- .imports (values .getImports ().toArray (new String [0 ]))
95
- .staticImports (values .getStaticImports ().toArray (new String [0 ]))
96
- .javaParser (JavaParser .fromJavaVersion ().classpath (JavaParser .runtimeClasspath ()))
97
- .build ()
98
- .apply (updateCursor (nc ), nc .getCoordinates ().replace (), template .getParameters ());
99
- return avoidMethodSelfReferences (nc , replacement );
98
+ return super .visitNewClass (newClass , ctx );
100
99
}
101
100
102
- private @ Nullable InlineMeValues findInlineMeValues (JavaType .@ Nullable Method methodType ) {
103
- if (methodType == null ) {
104
- return null ;
105
- }
106
- List <String > parameterNames = methodType .getParameterNames ();
107
- if (!parameterNames .isEmpty () && "arg0" .equals (parameterNames .get (0 ))) {
108
- return null ; // We need `-parameters` before we're able to substitute parameters in the template
109
- }
110
-
111
- List <JavaType .FullyQualified > annotations = methodType .getAnnotations ();
112
- for (JavaType .FullyQualified annotation : annotations ) {
113
- if (INLINE_ME .equals (annotation .getClassName ())) {
114
- return InlineMeValues .parse ((JavaType .Annotation ) annotation );
115
- }
116
- }
117
- return null ;
101
+ private J replaceMethodCall (MethodCall methodCall , ExecutionContext ctx ) {
102
+ Set <String > importsSet = imports != null ? imports : emptySet ();
103
+ Set <String > staticImportsSet = staticImports != null ? staticImports : emptySet ();
104
+ removeAndAddImports (methodCall , importsSet , staticImportsSet );
105
+ J applied = applyJavaTemplate (methodCall , getCursor (), importsSet , staticImportsSet , ctx );
106
+ return avoidMethodSelfReferences (methodCall , applied );
118
107
}
119
108
120
109
private void removeAndAddImports (MethodCall method , Set <String > templateImports , Set <String > templateStaticImports ) {
@@ -152,10 +141,10 @@ private Set<String> findOriginalImports(MethodCall method) {
152
141
// Collect all regular and static imports used in the original method call
153
142
return new JavaVisitor <Set <String >>() {
154
143
@ Override
155
- public @ Nullable JavaType visitType (@ Nullable JavaType javaType , Set <String > strings ) {
156
- JavaType jt = super .visitType (javaType , strings );
144
+ public @ Nullable JavaType visitType (@ Nullable JavaType javaType , Set <String > imports ) {
145
+ JavaType jt = super .visitType (javaType , imports );
157
146
if (jt instanceof JavaType .FullyQualified ) {
158
- strings .add (((JavaType .FullyQualified ) jt ).getFullyQualifiedName ());
147
+ imports .add (((JavaType .FullyQualified ) jt ).getFullyQualifiedName ());
159
148
}
160
149
return jt ;
161
150
}
@@ -190,6 +179,70 @@ public J visitIdentifier(J.Identifier identifier, Set<String> staticImports) {
190
179
}.reduce (method , new HashSet <>());
191
180
}
192
181
182
+ J applyJavaTemplate (MethodCall methodCall , Cursor cursor , Set <String > importsSet , Set <String > staticImportsSet , ExecutionContext ctx ) {
183
+ JavaType .Method methodType = requireNonNull (methodCall .getMethodType ());
184
+ String string = createTemplateString (methodCall , methodType );
185
+ Object [] parameters = createParameters (string , methodCall );
186
+
187
+ JavaTemplate .Builder templateBuilder = JavaTemplate .builder (string )
188
+ .contextSensitive ()
189
+ .imports (importsSet .toArray (new String [0 ]))
190
+ .staticImports (staticImportsSet .toArray (new String [0 ]));
191
+ if (classpathFromResources != null && !classpathFromResources .isEmpty ()) {
192
+ templateBuilder .javaParser (JavaParser .fromJavaVersion ()
193
+ .classpathFromResources (ctx , classpathFromResources .toArray (new String [0 ])));
194
+ }
195
+ return templateBuilder .build ()
196
+ .apply (cursor , methodCall .getCoordinates ().replace (), parameters );
197
+ }
198
+
199
+ private String createTemplateString (MethodCall original , JavaType .Method methodType ) {
200
+ String templateString ;
201
+ if (original instanceof J .NewClass && replacement .startsWith ("this(" )) {
202
+ // For constructor-to-constructor replacement, replace "this" with "new ClassName"
203
+ templateString = "new " + methodType .getDeclaringType ().getClassName () + replacement .substring (4 );
204
+ } else if (original instanceof J .MethodInvocation &&
205
+ ((J .MethodInvocation ) original ).getSelect () == null &&
206
+ replacement .startsWith ("this." )) {
207
+ templateString = replacement .substring (5 );
208
+ } else {
209
+ templateString = replacement .replaceAll ("\\ bthis\\ b" , "#{this:any()}" );
210
+ }
211
+ List <String > originalParameterNames = methodType .getParameterNames ();
212
+ for (String parameterName : originalParameterNames ) {
213
+ // Replace parameter names with their values in the templateString
214
+ templateString = templateString
215
+ .replaceFirst (format ("\\ b%s\\ b" , parameterName ), format ("#{%s:any()}" , parameterName ))
216
+ .replaceAll (format ("(?<!\\ {)\\ b%s\\ b" , parameterName ), format ("#{%s}" , parameterName ));
217
+ }
218
+ return templateString ;
219
+ }
220
+
221
+ private Object [] createParameters (String templateString , MethodCall original ) {
222
+ Map <String , Expression > lookup = new HashMap <>();
223
+ if (original instanceof J .MethodInvocation ) {
224
+ Expression select = ((J .MethodInvocation ) original ).getSelect ();
225
+ if (select != null ) {
226
+ lookup .put ("this" , select );
227
+ }
228
+ }
229
+ List <String > originalParameterNames = requireNonNull (original .getMethodType ()).getParameterNames ();
230
+ for (int i = 0 ; i < originalParameterNames .size (); i ++) {
231
+ String originalName = originalParameterNames .get (i );
232
+ Expression originalValue = original .getArguments ().get (i );
233
+ lookup .put (originalName , originalValue );
234
+ }
235
+ List <Object > parameters = new ArrayList <>();
236
+ Matcher matcher = TEMPLATE_IDENTIFIER .matcher (templateString );
237
+ while (matcher .find ()) {
238
+ Expression o = lookup .get (matcher .group (1 ));
239
+ if (o != null ) {
240
+ parameters .add (o );
241
+ }
242
+ }
243
+ return parameters .toArray ();
244
+ }
245
+
193
246
private J avoidMethodSelfReferences (MethodCall original , J replacement ) {
194
247
JavaType .Method replacementMethodType = replacement instanceof MethodCall ?
195
248
((MethodCall ) replacement ).getMethodType () : null ;
@@ -215,102 +268,6 @@ private J avoidMethodSelfReferences(MethodCall original, J replacement) {
215
268
}
216
269
return replacement ;
217
270
}
218
- };
219
- }
220
-
221
- @ Value
222
- private static class InlineMeValues {
223
- private static final Pattern TEMPLATE_IDENTIFIER = Pattern .compile ("#\\ {(\\ p{javaJavaIdentifierStart}\\ p{javaJavaIdentifierPart}*):any\\ (.*?\\ )}" );
224
-
225
- @ Getter (AccessLevel .NONE )
226
- String replacement ;
227
-
228
- Set <String > imports ;
229
- Set <String > staticImports ;
230
-
231
- static InlineMeValues parse (JavaType .Annotation annotation ) {
232
- Map <String , Object > collect = annotation .getValues ().stream ().collect (toMap (
233
- e -> ((JavaType .Method ) e .getElement ()).getName (),
234
- JavaType .Annotation .ElementValue ::getValue
235
- ));
236
- // Parse imports and static imports from the annotation values
237
- return new InlineMeValues (
238
- (String ) collect .get ("replacement" ),
239
- parseImports (collect .get ("imports" )),
240
- parseImports (collect .get ("staticImports" )));
241
- }
242
-
243
- private static Set <String > parseImports (@ Nullable Object importsValue ) {
244
- if (importsValue instanceof List ) {
245
- return ((List <?>) importsValue ).stream ()
246
- .map (Object ::toString )
247
- .collect (toSet ());
248
- }
249
- return emptySet ();
250
- }
251
-
252
- @ Nullable
253
- Template template (MethodCall original ) {
254
- JavaType .Method methodType = original .getMethodType ();
255
- if (methodType == null ) {
256
- return null ;
257
- }
258
- String templateString = createTemplateString (original , replacement , methodType );
259
- List <Object > parameters = createParameters (templateString , original );
260
- return new Template (templateString , parameters .toArray (new Object [0 ]));
261
- }
262
-
263
- private static String createTemplateString (MethodCall original , String replacement , JavaType .Method methodType ) {
264
- String templateString ;
265
- if (original instanceof J .NewClass && replacement .startsWith ("this(" )) {
266
- // For constructor-to-constructor replacement, replace "this" with "new ClassName"
267
- templateString = "new " + methodType .getDeclaringType ().getClassName () + replacement .substring (4 );
268
- } else if (original instanceof J .MethodInvocation &&
269
- ((J .MethodInvocation ) original ).getSelect () == null &&
270
- replacement .startsWith ("this." )) {
271
- templateString = replacement .substring (5 );
272
- } else {
273
- templateString = replacement .replaceAll ("\\ bthis\\ b" , "#{this:any()}" );
274
- }
275
- List <String > originalParameterNames = methodType .getParameterNames ();
276
- for (String parameterName : originalParameterNames ) {
277
- // Replace parameter names with their values in the templateString
278
- templateString = templateString
279
- .replaceFirst (format ("\\ b%s\\ b" , parameterName ), format ("#{%s:any()}" , parameterName ))
280
- .replaceAll (format ("(?<!\\ {)\\ b%s\\ b" , parameterName ), format ("#{%s}" , parameterName ));
281
- }
282
- return templateString ;
283
- }
284
-
285
- private static List <Object > createParameters (String templateString , MethodCall original ) {
286
- Map <String , Expression > lookup = new HashMap <>();
287
- if (original instanceof J .MethodInvocation ) {
288
- Expression select = ((J .MethodInvocation ) original ).getSelect ();
289
- if (select != null ) {
290
- lookup .put ("this" , select );
291
- }
292
- }
293
- List <String > originalParameterNames = requireNonNull (original .getMethodType ()).getParameterNames ();
294
- for (int i = 0 ; i < originalParameterNames .size (); i ++) {
295
- String originalName = originalParameterNames .get (i );
296
- Expression originalValue = original .getArguments ().get (i );
297
- lookup .put (originalName , originalValue );
298
- }
299
- List <Object > parameters = new ArrayList <>();
300
- Matcher matcher = TEMPLATE_IDENTIFIER .matcher (templateString );
301
- while (matcher .find ()) {
302
- Expression o = lookup .get (matcher .group (1 ));
303
- if (o != null ) {
304
- parameters .add (o );
305
- }
306
- }
307
- return parameters ;
308
- }
309
- }
310
-
311
- @ Value
312
- private static class Template {
313
- String string ;
314
- Object [] parameters ;
271
+ });
315
272
}
316
273
}
0 commit comments