@@ -191,3 +191,240 @@ export const rebuildCodemodFallback = async (options: {
191
191
192
192
export const oraCheckmark = chalk . green ( "✔" ) ;
193
193
export const oraCross = chalk . red ( "✖" ) ;
194
+
195
+ const generateCodemodContext = `### Context
196
+ - You will be provided with BEFORE and AFTER code snippet pairs.
197
+ - Write a single jscodeshift codemod that transforms each BEFORE snippet into the AFTER snippet.
198
+ - Identify common patterns and create a generic codemod to handle all cases.
199
+ - Use only jscodeshift and TypeScript.
200
+ - If comments in AFTER snippets describe the transformation, do not preserve them.
201
+ - Only include a code block in your response, no extra explanations.
202
+ - Comment your code following best practices.
203
+ - Do not import 'namedTypes' or 'builders' from jscodeshift.
204
+ - Always narrow node types using typeguards before accessing their properties.
205
+ ` ;
206
+
207
+ const improveCodemodContext = `### Context
208
+ - You will be provided with BEFORE and AFTER code snippet pairs and an existing codemod that might or might not satisfy them.
209
+ - Use the provided jscodeshift codemod and see whether it would turn each BEFORE snippet into corresponding AFTER snippet.
210
+ - Identify common patterns and improve the codemod to handle all cases.
211
+ - Use only jscodeshift and TypeScript.
212
+ - If comments in AFTER snippets describe the transformation, do not preserve them.
213
+ - Only include the generated codemod in your response, no extra explanations.
214
+ - Comment your code following best practices.
215
+ - Do not import 'namedTypes' or 'builders' from jscodeshift.
216
+ - Always narrow node types using typeguards before accessing their properties.
217
+ ` ;
218
+
219
+ const jscodeshiftUsageExamples = `Here are two examples of using typeguards to check whether the import source is a string literal:
220
+ \`\`\`typescript
221
+ if (j.Literal.check(node.source)) { // CORRECT
222
+ // rest of the code
223
+ }
224
+
225
+ if (j.Literal.check(node.source) && typeof node.source.value === 'string') { // CORRECT
226
+ // rest of the code
227
+ }
228
+ \`\`\`
229
+
230
+
231
+ - Never check the node type without using typeguards. The following example is INCORRECT:
232
+ \`\`\`typescript
233
+ if (node.source.type === 'Literal') { // INCORRECT
234
+ // rest of the code
235
+ }
236
+ \`\`\`
237
+
238
+ ### Examples
239
+ #### Example 1
240
+ **BEFORE**:
241
+ \`\`\`typescript
242
+ import { A } from '@ember/array';
243
+ let arr = new A();
244
+ \`\`\`
245
+ **AFTER**:
246
+ \`\`\`typescript
247
+ import { A as emberA } from '@ember/array';
248
+ let arr = A();
249
+ \`\`\`
250
+ **CODEMOD**:
251
+ \`\`\`typescript
252
+ export default function transform(file, api) {
253
+ const j = api.jscodeshift;
254
+ const root = j(file.source);
255
+ root.find(j.NewExpression, { callee: { name: "A" } }).replaceWith(() => {
256
+ root.find(j.ImportSpecifier, {
257
+ imported: { name: "A" },
258
+ local: { name: "A" }
259
+ }).replaceWith(() => {
260
+ return j.importSpecifier(j.identifier("A"), j.identifier("emberA"));
261
+ });
262
+ return j.callExpression(j.identifier("A"), []);
263
+ });
264
+ return root.toSource();
265
+ }
266
+ \`\`\`
267
+
268
+ #### Example 2
269
+ **BEFORE**:
270
+ \`\`\`typescript
271
+ import { Route, Router } from 'react-router-dom';
272
+ const MyApp = () => (
273
+ <Router history={history}>
274
+ <Route path='/posts' component={PostList} />
275
+ <Route path='/posts/:id' component={PostEdit} />
276
+ <Route path='/posts/:id/show' component={PostShow} />
277
+ <Route path='/posts/:id/delete' component={PostDelete} />
278
+ </Router>
279
+ );
280
+ \`\`\`
281
+ **AFTER**:
282
+ \`\`\`typescript
283
+ import { Route, Router } from 'react-router-dom';
284
+ const MyApp = () => (
285
+ <Router history={history}>
286
+ <Switch>
287
+ <Route path='/posts' component={PostList} />
288
+ <Route path='/posts/:id' component={PostEdit} />
289
+ <Route path='/posts/:id/show' component={PostShow} />
290
+ <Route path='/posts/:id/delete' component={PostDelete} />
291
+ </Switch>
292
+ </Router>
293
+ );
294
+ \`\`\`
295
+ **CODEMOD**:
296
+ \`\`\`typescript
297
+ import type { API, FileInfo, Options, Transform } from "jscodeshift";
298
+ function transform(file: FileInfo, api: API, options: Options): string | undefined {
299
+ const j = api.jscodeshift;
300
+ const root = j(file.source);
301
+ root.find(j.JSXElement, {
302
+ openingElement: { name: { name: "Router" } }
303
+ }).forEach((path) => {
304
+ const hasSwitch = root.findJSXElements("Switch").length > 0;
305
+ if (hasSwitch) {
306
+ return;
307
+ }
308
+ const children = path.value.children;
309
+ const newEl = j.jsxElement(
310
+ j.jsxOpeningElement(j.jsxIdentifier("Switch"), [], false),
311
+ j.jsxClosingElement(j.jsxIdentifier("Switch")),
312
+ children
313
+ );
314
+ path.value.children = [j.jsxText("\\n "), newEl, j.jsxText("\\n")];
315
+ });
316
+ return root.toSource(options);
317
+ }
318
+ export default transform;
319
+ \`\`\`
320
+
321
+ #### Example 3
322
+ **BEFORE**:
323
+ \`\`\`typescript
324
+ import { Redirect, Route } from 'react-router';
325
+ \`\`\`
326
+ **AFTER**:
327
+ \`\`\`typescript
328
+ import { Redirect, Route } from 'react-router-dom';
329
+ \`\`\`
330
+ **CODEMOD**:
331
+ \`\`\`typescript
332
+ import type { API, FileInfo, Options, Transform } from 'jscodeshift';
333
+ function transform(file: FileInfo, api: API, options: Options): string | undefined {
334
+ const j = api.jscodeshift;
335
+ const root = j(file.source);
336
+ root.find(j.ImportDeclaration, {
337
+ source: { value: 'react-router' }
338
+ }).forEach((path) => {
339
+ path.value.source.value = 'react-router-dom';
340
+ });
341
+ return root.toSource(options);
342
+ }
343
+ export default transform;
344
+ \`\`\`
345
+
346
+ ## Additional API about jscodeshift
347
+ ### closestScope: Finds the closest enclosing scope of a node. Useful for determining the scope context of variables and functions.
348
+ \`\`\`typescript
349
+ const closestScopes = j.find(j.Identifier).closestScope();
350
+ \`\`\`
351
+
352
+ ### some: checks if at least one element in the collection passes the test implemented by the provided function.
353
+ \`\`\`typescript
354
+ const hasVariableA = root.find(j.VariableDeclarator).some(path => path.node.id.name === 'a');
355
+ \`\`\`
356
+
357
+ ### map: Maps each node in the collection to a new value.
358
+ \`\`\`typescript
359
+ const variableNames = j.find(j.VariableDeclaration).map(path => path.node.declarations.map(decl => decl.id.name));
360
+ \`\`\`
361
+
362
+ ### paths: Returns the paths of the found nodes.
363
+ \`\`\`typescript
364
+ const paths = j.find(j.VariableDeclaration).paths();
365
+ \`\`\`
366
+
367
+ ### get: Gets the first node in the collection.
368
+ \`\`\`typescript
369
+ const firstVariableDeclaration = j.find(j.VariableDeclaration).get();
370
+ \`\`\`
371
+
372
+ ### at: Navigates to a specific path in the AST.
373
+ \`\`\`typescript
374
+ const secondVariableDeclaration = j.find(j.VariableDeclaration).at(1);
375
+ \`\`\`
376
+
377
+ ### isOfType: checks if the node in the collection is of a specific type.
378
+ \`\`\`typescript
379
+ const isVariableDeclarator = root.find(j.VariableDeclarator).at(0).isOfType('VariableDeclarator');
380
+ \`\`\`
381
+ ` ;
382
+
383
+ export function getCodemodPrompt (
384
+ type : "generate" ,
385
+ testCases : { before : string ; after : string } [ ] ,
386
+ ) : string ;
387
+ export function getCodemodPrompt (
388
+ type : "improve" ,
389
+ testCases : { before : string ; after : string } [ ] ,
390
+ existingCodemodSource : string ,
391
+ ) : string ;
392
+ export function getCodemodPrompt (
393
+ type : "improve" | "generate" ,
394
+ testCases : { before : string ; after : string } [ ] ,
395
+ existingCodemodSource ?: string ,
396
+ ) {
397
+ return `${ type === "generate" ? generateCodemodContext : improveCodemodContext }
398
+
399
+ ${
400
+ type === "improve"
401
+ ? `\n\n### Existing Codemod
402
+ \`\`\`typescript
403
+ ${ existingCodemodSource }
404
+ \`\`\`
405
+ `
406
+ : ""
407
+ }
408
+
409
+ ### Input Snippets
410
+
411
+
412
+ ${ testCases
413
+ . map (
414
+ ( { before, after } , i ) => `## Input ${ i + 1 }
415
+ **BEFORE**:
416
+ \`\`\`typescript
417
+ ${ before }
418
+ \`\`\`
419
+ **AFTER**:
420
+ \`\`\`typescript
421
+ ${ after }
422
+ \`\`\`
423
+ ` ,
424
+ )
425
+ . join ( "\n" ) }
426
+
427
+
428
+ ${ jscodeshiftUsageExamples }
429
+ ` ;
430
+ }
0 commit comments