@@ -192,6 +192,139 @@ def test_builder_chaining(self):
192192 assert builder .imports ("import z" ) is builder
193193
194194
195+ class TestTemplateApplyRecipe :
196+ """End-to-end tests for pattern match + template apply in recipes."""
197+
198+ def test_replace_method_call_with_assignment (self ):
199+ """Test that pattern({obj}.setX({val})) + template({obj}.x = {val}) produces '=' not ':='."""
200+ from rewrite import ExecutionContext , Recipe , TreeVisitor
201+ from rewrite .python .visitor import PythonVisitor
202+ from rewrite .python .template import pattern , capture
203+ from rewrite .java .tree import MethodInvocation
204+ from rewrite .test import RecipeSpec , python
205+
206+ obj = capture ('obj' )
207+ val = capture ('val' )
208+ pat = pattern ("{obj}.setDaemon({val})" , obj = obj , val = val )
209+ tmpl = template ("{obj}.daemon = {val}" , obj = obj , val = val )
210+
211+ class TestRecipe (Recipe ):
212+ @property
213+ def name (self ) -> str :
214+ return "test.ReplaceSetterWithAssignment"
215+
216+ @property
217+ def display_name (self ) -> str :
218+ return "Test"
219+
220+ @property
221+ def description (self ) -> str :
222+ return "Test"
223+
224+ def editor (self ):
225+ class Visitor (PythonVisitor [ExecutionContext ]):
226+ def visit_method_invocation (self , method , p ):
227+ method = super ().visit_method_invocation (method , p )
228+ match = pat .match (method , self .cursor )
229+ if match :
230+ return tmpl .apply (self .cursor , values = match )
231+ return method
232+ return Visitor ()
233+
234+ spec = RecipeSpec (recipe = TestRecipe ())
235+ spec .rewrite_run (
236+ python (
237+ "thread.setDaemon(True)" ,
238+ "thread.daemon = True" ,
239+ )
240+ )
241+
242+ def test_replace_method_call_with_method_call (self ):
243+ """Test that pattern(datetime.utcnow()) + template(datetime.now(datetime.UTC)) works."""
244+ from rewrite import ExecutionContext , Recipe , TreeVisitor
245+ from rewrite .python .visitor import PythonVisitor
246+ from rewrite .python .template import pattern , capture
247+ from rewrite .java .tree import MethodInvocation
248+ from rewrite .test import RecipeSpec , python
249+
250+ pat = pattern ("datetime.utcnow()" )
251+ tmpl = template ("datetime.now(datetime.UTC)" )
252+
253+ class TestRecipe (Recipe ):
254+ @property
255+ def name (self ) -> str :
256+ return "test.ReplaceUtcNow"
257+
258+ @property
259+ def display_name (self ) -> str :
260+ return "Test"
261+
262+ @property
263+ def description (self ) -> str :
264+ return "Test"
265+
266+ def editor (self ):
267+ class Visitor (PythonVisitor [ExecutionContext ]):
268+ def visit_method_invocation (self , method , p ):
269+ method = super ().visit_method_invocation (method , p )
270+ match = pat .match (method , self .cursor )
271+ if match :
272+ return tmpl .apply (self .cursor , values = match )
273+ return method
274+ return Visitor ()
275+
276+ spec = RecipeSpec (recipe = TestRecipe ())
277+ spec .rewrite_run (
278+ python (
279+ "now = datetime.utcnow()" ,
280+ "now = datetime.now(datetime.UTC)" ,
281+ )
282+ )
283+
284+ def test_replace_method_call_with_field_access (self ):
285+ """Test that pattern({obj}.getName()) + template({obj}.name) works."""
286+ from rewrite import ExecutionContext , Recipe , TreeVisitor
287+ from rewrite .python .visitor import PythonVisitor
288+ from rewrite .python .template import pattern , capture
289+ from rewrite .java .tree import MethodInvocation
290+ from rewrite .test import RecipeSpec , python
291+
292+ obj = capture ('obj' )
293+ pat = pattern ("{obj}.getName()" , obj = obj )
294+ tmpl = template ("{obj}.name" , obj = obj )
295+
296+ class TestRecipe (Recipe ):
297+ @property
298+ def name (self ) -> str :
299+ return "test.ReplaceGetterWithProperty"
300+
301+ @property
302+ def display_name (self ) -> str :
303+ return "Test"
304+
305+ @property
306+ def description (self ) -> str :
307+ return "Test"
308+
309+ def editor (self ):
310+ class Visitor (PythonVisitor [ExecutionContext ]):
311+ def visit_method_invocation (self , method , p ):
312+ method = super ().visit_method_invocation (method , p )
313+ match = pat .match (method , self .cursor )
314+ if match :
315+ return tmpl .apply (self .cursor , values = match )
316+ return method
317+ return Visitor ()
318+
319+ spec = RecipeSpec (recipe = TestRecipe ())
320+ spec .rewrite_run (
321+ python (
322+ "name = thread.getName()" ,
323+ "name = thread.name" ,
324+ )
325+ )
326+
327+
195328class TestTemplateApply :
196329 """Tests for Template.apply()."""
197330
0 commit comments