Skip to content

Commit 7255d6e

Browse files
committed
chore: rework to ast-grep
1 parent 3709a3f commit 7255d6e

File tree

11 files changed

+171
-114
lines changed

11 files changed

+171
-114
lines changed

codemods/iterate-iterator/index.js

Lines changed: 91 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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';
1313
export 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
}

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import isString from './codemods/is-string/index.js';
9090
import isTravis from './codemods/is-travis/index.js';
9191
import isWhitespace from './codemods/is-whitespace/index.js';
9292
import isWindows from './codemods/is-windows/index.js';
93+
import iterateIterator from './codemods/iterate-iterator/index.js';
9394
import iterateValue from './codemods/iterate-value/index.js';
9495
import lastIndexOf from './codemods/last-index-of/index.js';
9596
import leftPad from './codemods/left-pad/index.js';
@@ -263,6 +264,7 @@ export const codemods = {
263264
"is-travis": isTravis,
264265
"is-whitespace": isWhitespace,
265266
"is-windows": isWindows,
267+
"iterate-iterator": iterateIterator,
266268
"iterate-value": iterateValue,
267269
"last-index-of": lastIndexOf,
268270
"left-pad": leftPad,

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@
408408
"types": "./types/codemods/is-windows/index.d.ts",
409409
"default": "./codemods/is-windows/index.js"
410410
},
411+
"./codemods/iterate-iterator/index.js": {
412+
"types": "./types/codemods/iterate-iterator/index.d.ts",
413+
"default": "./codemods/iterate-iterator/index.js"
414+
},
411415
"./codemods/iterate-value/index.js": {
412416
"types": "./types/codemods/iterate-value/index.d.ts",
413417
"default": "./codemods/iterate-value/index.js"

test/fixtures/iterate-iterator/case-1/after.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import assert from 'assert';
23

34
assert.deepEqual(Array.from({
@@ -24,7 +25,7 @@ const foo = {
2425
}
2526
}
2627
};
27-
assert.deepStrictEqual(Array.from({
28+
assert.deepStrictEqual(Array.from({
2829
[Symbol.iterator]: () => foo
2930
}), [42, 42, 42, 42, 42]);
3031

@@ -33,10 +34,10 @@ function assertWithCallback(iterable, expected) {
3334
const callback = function (x) { values.push(x); };
3435

3536
for (const i of {
36-
[Symbol.iterator]: () => iterable
37-
}) {
38-
callback(i);
39-
};
37+
[Symbol.iterator]: () => iterable
38+
}) {
39+
callback(i);
40+
};
4041

4142
assert.deepEqual(values, expected);
4243
}
@@ -52,8 +53,8 @@ for (const i of {
5253
(function (x) { console.log(x); })(i);
5354
};
5455

55-
for (const i of {
56+
for (const x of {
5657
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
5758
}) {
58-
(x => console.log(x))(i);
59+
console.log(x);
5960
};

test/fixtures/iterate-iterator/case-1/result.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import assert from 'assert';
23

34
assert.deepEqual(Array.from({
@@ -24,7 +25,7 @@ const foo = {
2425
}
2526
}
2627
};
27-
assert.deepStrictEqual(Array.from({
28+
assert.deepStrictEqual(Array.from({
2829
[Symbol.iterator]: () => foo
2930
}), [42, 42, 42, 42, 42]);
3031

@@ -33,10 +34,10 @@ function assertWithCallback(iterable, expected) {
3334
const callback = function (x) { values.push(x); };
3435

3536
for (const i of {
36-
[Symbol.iterator]: () => iterable
37-
}) {
38-
callback(i);
39-
};
37+
[Symbol.iterator]: () => iterable
38+
}) {
39+
callback(i);
40+
};
4041

4142
assert.deepEqual(values, expected);
4243
}
@@ -52,8 +53,8 @@ for (const i of {
5253
(function (x) { console.log(x); })(i);
5354
};
5455

55-
for (const i of {
56+
for (const x of {
5657
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
5758
}) {
58-
(x => console.log(x))(i);
59+
console.log(x);
5960
};

test/fixtures/iterate-iterator/case-2/after.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const assert = require('assert');
22

3+
34
assert.deepEqual(Array.from({
45
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
56
}), ['a', ' ', '💩']);
@@ -24,7 +25,7 @@ const foo = {
2425
}
2526
}
2627
};
27-
assert.deepStrictEqual(Array.from({
28+
assert.deepStrictEqual(Array.from({
2829
[Symbol.iterator]: () => foo
2930
}), [42, 42, 42, 42, 42]);
3031

@@ -33,10 +34,10 @@ function assertWithCallback(iterable, expected) {
3334
const callback = function (x) { values.push(x); };
3435

3536
for (const i of {
36-
[Symbol.iterator]: () => iterable
37-
}) {
38-
callback(i);
39-
};
37+
[Symbol.iterator]: () => iterable
38+
}) {
39+
callback(i);
40+
};
4041

4142
assert.deepEqual(values, expected);
4243
}
@@ -52,8 +53,8 @@ for (const i of {
5253
(function (x) { console.log(x); })(i);
5354
};
5455

55-
for (const i of {
56+
for (const x of {
5657
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
5758
}) {
58-
(x => console.log(x))(i);
59+
console.log(x);
5960
};

test/fixtures/iterate-iterator/case-2/result.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const assert = require('assert');
22

3+
34
assert.deepEqual(Array.from({
45
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
56
}), ['a', ' ', '💩']);
@@ -24,7 +25,7 @@ const foo = {
2425
}
2526
}
2627
};
27-
assert.deepStrictEqual(Array.from({
28+
assert.deepStrictEqual(Array.from({
2829
[Symbol.iterator]: () => foo
2930
}), [42, 42, 42, 42, 42]);
3031

@@ -33,10 +34,10 @@ function assertWithCallback(iterable, expected) {
3334
const callback = function (x) { values.push(x); };
3435

3536
for (const i of {
36-
[Symbol.iterator]: () => iterable
37-
}) {
38-
callback(i);
39-
};
37+
[Symbol.iterator]: () => iterable
38+
}) {
39+
callback(i);
40+
};
4041

4142
assert.deepEqual(values, expected);
4243
}
@@ -52,8 +53,8 @@ for (const i of {
5253
(function (x) { console.log(x); })(i);
5354
};
5455

55-
for (const i of {
56+
for (const x of {
5657
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
5758
}) {
58-
(x => console.log(x))(i);
59+
console.log(x);
5960
};

0 commit comments

Comments
 (0)