1- import jscodeshift from 'jscodeshift ' ;
2- import { removeImport } from '../shared.js' ;
1+ import { ts } from '@ast-grep/napi ' ;
2+ import { findNamedDefaultImport } from '../shared-ast-grep .js' ;
33
44/**
55 * @typedef {import('../../types.js').Codemod } Codemod
@@ -13,105 +13,110 @@ import { removeImport } from '../shared.js';
1313export default function ( options ) {
1414 return {
1515 name : 'iterate-iterator' ,
16+ to : 'native' ,
1617 transform : ( { file } ) => {
17- const j = jscodeshift ;
18- const root = j ( file . source ) ;
19- let isDirty = false ;
18+ const ast = ts . parse ( file . source ) ;
19+ const root = ast . root ( ) ;
20+ const edits = [ ] ;
21+ const importNames = new Set ( ) ;
2022
21- const { identifier } = removeImport ( 'iterate-iterator' , root , j ) ;
23+ const imports = findNamedDefaultImport ( root , 'iterate-iterator' ) ;
2224
23- if ( identifier ) {
24- const callExpressions = root . find ( j . CallExpression , {
25- callee : { type : 'Identifier' , name : identifier } ,
25+ for ( const imp of imports ) {
26+ const nameMatch = imp . getMatch ( 'NAME' ) ;
27+ if ( nameMatch ) {
28+ importNames . add ( nameMatch . text ( ) ) ;
29+ }
30+ edits . push ( imp . replace ( '' ) ) ;
31+ }
32+
33+ for ( const importName of importNames ) {
34+ const singleArgCalls = root . findAll ( {
35+ rule : {
36+ pattern : {
37+ context : `${ importName } ($ARG)` ,
38+ strictness : 'relaxed' ,
39+ } ,
40+ } ,
2641 } ) ;
2742
28- for ( const path of callExpressions . paths ( ) ) {
29- const args = path . node . arguments ;
30- if ( args . length === 1 ) {
31- // Case: Converting an iterator to an array
32- const [ iterable ] = args ;
33- const iterableArg =
34- iterable . type === 'SpreadElement' ? iterable . argument : iterable ;
35-
36- const wrappedIterable = j . objectExpression ( [
37- j . property (
38- 'init' ,
39- j . memberExpression (
40- j . identifier ( 'Symbol' ) ,
41- j . identifier ( 'iterator' ) ,
42- ) ,
43- j . arrowFunctionExpression ( [ ] , iterableArg ) ,
44- ) ,
45- ] ) ;
46-
47- if (
48- wrappedIterable . properties [ 0 ] . type !== 'SpreadProperty' &&
49- wrappedIterable . properties [ 0 ] . type !== 'SpreadElement'
50- ) {
51- wrappedIterable . properties [ 0 ] . computed = true ;
52- }
43+ for ( const call of singleArgCalls ) {
44+ const argMatch = call . getMatch ( 'ARG' ) ;
45+ if ( argMatch ) {
46+ const replacement = `Array.from({\n [Symbol.iterator]: () => ${ argMatch . text ( ) } \n})` ;
47+ edits . push ( call . replace ( replacement ) ) ;
48+ }
49+ }
50+
51+ const arrowCalls = root . findAll ( {
52+ rule : {
53+ pattern : {
54+ context : `${ importName } ($ARG1, $PARAM => $BODY)` ,
55+ strictness : 'relaxed' ,
56+ } ,
57+ } ,
58+ constraints : {
59+ PARAM : { regex : '^[^,]+$' } ,
60+ } ,
61+ } ) ;
5362
54- const arrayFromExpression = j . callExpression (
55- j . memberExpression ( j . identifier ( 'Array' ) , j . identifier ( 'from' ) ) ,
56- [ wrappedIterable ] ,
57- ) ;
58- j ( path ) . replaceWith ( arrayFromExpression ) ;
59- isDirty = true ;
60- } else if ( args . length === 2 ) {
61- // Case: Using a callback function
62- const [ iterable , callback ] = args ;
63- const iterableArg =
64- iterable . type === 'SpreadElement' ? iterable . argument : iterable ;
65-
66- if (
67- callback . type !== 'Identifier' &&
68- callback . type !== 'FunctionExpression' &&
69- callback . type !== 'ArrowFunctionExpression'
70- ) {
63+ for ( const call of arrowCalls ) {
64+ const arg1Match = call . getMatch ( 'ARG1' ) ;
65+ const paramMatch = call . getMatch ( 'PARAM' ) ;
66+ const bodyMatch = call . getMatch ( 'BODY' ) ;
67+
68+ if ( arg1Match && paramMatch && bodyMatch ) {
69+ if ( bodyMatch . kind ( ) === 'statement_block' ) {
7170 continue ;
7271 }
7372
74- const wrappedIterable = j . objectExpression ( [
75- j . property (
76- 'init' ,
77- j . memberExpression (
78- j . identifier ( 'Symbol' ) ,
79- j . identifier ( 'iterator' ) ,
80- ) ,
81- j . arrowFunctionExpression ( [ ] , iterableArg ) ,
82- ) ,
83- ] ) ;
84-
85- if (
86- wrappedIterable . properties [ 0 ] . type !== 'SpreadProperty' &&
87- wrappedIterable . properties [ 0 ] . type !== 'SpreadElement'
88- ) {
89- wrappedIterable . properties [ 0 ] . computed = true ;
73+ const paramName = paramMatch . text ( ) . replace ( / ^ \( | \) $ / g, '' ) ;
74+ const bodyText = bodyMatch . text ( ) ;
75+
76+ const replacement = `for (const ${ paramName } of {\n [Symbol.iterator]: () => ${ arg1Match . text ( ) } \n}) {\n ${ bodyText } ;\n}` ;
77+ edits . push ( call . replace ( replacement ) ) ;
78+ }
79+ }
80+
81+ const otherCalls = root . findAll ( {
82+ rule : {
83+ pattern : {
84+ context : `${ importName } ($ARG1, $ARG2)` ,
85+ strictness : 'relaxed' ,
86+ } ,
87+ } ,
88+ } ) ;
89+
90+ for ( const call of otherCalls ) {
91+ const arg1Match = call . getMatch ( 'ARG1' ) ;
92+ const arg2Match = call . getMatch ( 'ARG2' ) ;
93+
94+ if ( arg1Match && arg2Match ) {
95+ const callback = arg2Match . text ( ) ;
96+ const callbackKind = arg2Match . kind ( ) ;
97+
98+ if ( callbackKind === 'arrow_function' ) {
99+ const body = arg2Match . field ( 'body' ) ;
100+ if ( body && body . kind ( ) !== 'statement_block' ) {
101+ continue ;
102+ }
90103 }
91104
92- const forOfStatement = j . forOfStatement (
93- j . variableDeclaration ( 'const' , [
94- j . variableDeclarator ( j . identifier ( 'i' ) ) ,
95- ] ) ,
96- wrappedIterable ,
97- j . blockStatement ( [
98- j . expressionStatement (
99- j . callExpression (
100- callback . type === 'Identifier'
101- ? callback
102- : j . parenthesizedExpression ( callback ) ,
103- [ j . identifier ( 'i' ) ] ,
104- ) ,
105- ) ,
106- ] ) ,
107- ) ;
108- j ( path ) . replaceWith ( forOfStatement ) ;
109- isDirty = true ;
105+ const needsParens =
106+ callbackKind === 'function_expression' ||
107+ callbackKind === 'arrow_function' ;
108+
109+ const callbackCall = needsParens
110+ ? `(${ callback } )(i)`
111+ : `${ callback } (i)` ;
112+
113+ const replacement = `for (const i of {\n [Symbol.iterator]: () => ${ arg1Match . text ( ) } \n}) {\n ${ callbackCall } ;\n}` ;
114+ edits . push ( call . replace ( replacement ) ) ;
110115 }
111116 }
112117 }
113118
114- return isDirty ? root . toSource ( options ) : file . source ;
119+ return root . commitEdits ( edits ) ;
115120 } ,
116121 } ;
117122}
0 commit comments