Skip to content

Commit 37a9feb

Browse files
author
Brady Blair
committed
feat: add codemod for iterate-iterator
refactor: use [Symbol.iterator]
1 parent 574e0c2 commit 37a9feb

File tree

7 files changed

+429
-0
lines changed

7 files changed

+429
-0
lines changed

codemods/iterate-iterator/index.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import jscodeshift from 'jscodeshift';
2+
import { removeImport } from '../shared.js';
3+
4+
/**
5+
* @typedef {import('../../types.js').Codemod} Codemod
6+
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
7+
*/
8+
9+
/**
10+
* @param {CodemodOptions} [options]
11+
* @returns {Codemod}
12+
*/
13+
export default function (options) {
14+
return {
15+
name: 'iterate-iterator',
16+
transform: ({ file }) => {
17+
const j = jscodeshift;
18+
const root = j(file.source);
19+
let isDirty = false;
20+
21+
const { identifier } = removeImport('iterate-iterator', root, j);
22+
23+
if (identifier) {
24+
const callExpressions = root.find(j.CallExpression, {
25+
callee: { type: 'Identifier', name: identifier },
26+
});
27+
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+
}
53+
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+
) {
71+
continue;
72+
}
73+
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;
90+
}
91+
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;
110+
}
111+
}
112+
}
113+
114+
return isDirty ? root.toSource(options) : file.source;
115+
},
116+
};
117+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import assert from 'assert';
2+
3+
assert.deepEqual(Array.from({
4+
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
5+
}), ['a', ' ', '💩']);
6+
assert.deepEqual(Array.from({
7+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
8+
}), [1, 2]);
9+
assert.deepEqual(Array.from({
10+
[Symbol.iterator]: () => new Set([1, 2]).values()
11+
}), [1, 2]);
12+
assert.deepEqual(Array.from({
13+
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
14+
}), [[1, 2], [3, 4]]);
15+
16+
const foo = {
17+
count: 0,
18+
next() {
19+
if (this.count < 5) {
20+
this.count++;
21+
return { done: false, value: 42 };
22+
} else {
23+
return { done: true };
24+
}
25+
}
26+
};
27+
assert.deepStrictEqual(Array.from({
28+
[Symbol.iterator]: () => foo
29+
}), [42, 42, 42, 42, 42]);
30+
31+
function assertWithCallback(iterable, expected) {
32+
const values = [];
33+
const callback = function (x) { values.push(x); };
34+
35+
for (const i of {
36+
[Symbol.iterator]: () => iterable
37+
}) {
38+
callback(i);
39+
};
40+
41+
assert.deepEqual(values, expected);
42+
}
43+
44+
assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
45+
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
46+
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
47+
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);
48+
49+
for (const i of {
50+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
51+
}) {
52+
(function (x) { console.log(x); })(i);
53+
};
54+
55+
for (const i of {
56+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
57+
}) {
58+
(x => console.log(x))(i);
59+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import iterateIterator from 'iterate-iterator';
2+
import assert from 'assert';
3+
4+
assert.deepEqual(iterateIterator('a 💩'[Symbol.iterator]()), ['a', ' ', '💩']);
5+
assert.deepEqual(iterateIterator([1, 2][Symbol.iterator]()), [1, 2]);
6+
assert.deepEqual(iterateIterator(new Set([1, 2]).values()), [1, 2]);
7+
assert.deepEqual(iterateIterator(new Map([[1, 2], [3, 4]]).entries()), [[1, 2], [3, 4]]);
8+
9+
const foo = {
10+
count: 0,
11+
next() {
12+
if (this.count < 5) {
13+
this.count++;
14+
return { done: false, value: 42 };
15+
} else {
16+
return { done: true };
17+
}
18+
}
19+
};
20+
assert.deepStrictEqual(iterateIterator(foo), [42, 42, 42, 42, 42]);
21+
22+
function assertWithCallback(iterable, expected) {
23+
const values = [];
24+
const callback = function (x) { values.push(x); };
25+
26+
iterateIterator(iterable, callback);
27+
28+
assert.deepEqual(values, expected);
29+
}
30+
31+
assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
32+
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
33+
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
34+
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);
35+
36+
iterateIterator([1, 2][Symbol.iterator](), function (x) { console.log(x); });
37+
38+
iterateIterator([1, 2][Symbol.iterator](), x => console.log(x));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import assert from 'assert';
2+
3+
assert.deepEqual(Array.from({
4+
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
5+
}), ['a', ' ', '💩']);
6+
assert.deepEqual(Array.from({
7+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
8+
}), [1, 2]);
9+
assert.deepEqual(Array.from({
10+
[Symbol.iterator]: () => new Set([1, 2]).values()
11+
}), [1, 2]);
12+
assert.deepEqual(Array.from({
13+
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
14+
}), [[1, 2], [3, 4]]);
15+
16+
const foo = {
17+
count: 0,
18+
next() {
19+
if (this.count < 5) {
20+
this.count++;
21+
return { done: false, value: 42 };
22+
} else {
23+
return { done: true };
24+
}
25+
}
26+
};
27+
assert.deepStrictEqual(Array.from({
28+
[Symbol.iterator]: () => foo
29+
}), [42, 42, 42, 42, 42]);
30+
31+
function assertWithCallback(iterable, expected) {
32+
const values = [];
33+
const callback = function (x) { values.push(x); };
34+
35+
for (const i of {
36+
[Symbol.iterator]: () => iterable
37+
}) {
38+
callback(i);
39+
};
40+
41+
assert.deepEqual(values, expected);
42+
}
43+
44+
assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
45+
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
46+
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
47+
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);
48+
49+
for (const i of {
50+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
51+
}) {
52+
(function (x) { console.log(x); })(i);
53+
};
54+
55+
for (const i of {
56+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
57+
}) {
58+
(x => console.log(x))(i);
59+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const assert = require('assert');
2+
3+
assert.deepEqual(Array.from({
4+
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
5+
}), ['a', ' ', '💩']);
6+
assert.deepEqual(Array.from({
7+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
8+
}), [1, 2]);
9+
assert.deepEqual(Array.from({
10+
[Symbol.iterator]: () => new Set([1, 2]).values()
11+
}), [1, 2]);
12+
assert.deepEqual(Array.from({
13+
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
14+
}), [[1, 2], [3, 4]]);
15+
16+
const foo = {
17+
count: 0,
18+
next() {
19+
if (this.count < 5) {
20+
this.count++;
21+
return { done: false, value: 42 };
22+
} else {
23+
return { done: true };
24+
}
25+
}
26+
};
27+
assert.deepStrictEqual(Array.from({
28+
[Symbol.iterator]: () => foo
29+
}), [42, 42, 42, 42, 42]);
30+
31+
function assertWithCallback(iterable, expected) {
32+
const values = [];
33+
const callback = function (x) { values.push(x); };
34+
35+
for (const i of {
36+
[Symbol.iterator]: () => iterable
37+
}) {
38+
callback(i);
39+
};
40+
41+
assert.deepEqual(values, expected);
42+
}
43+
44+
assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
45+
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
46+
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
47+
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);
48+
49+
for (const i of {
50+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
51+
}) {
52+
(function (x) { console.log(x); })(i);
53+
};
54+
55+
for (const i of {
56+
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
57+
}) {
58+
(x => console.log(x))(i);
59+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const assert = require('assert');
2+
const iterate = require('iterate-iterator');
3+
4+
assert.deepEqual(iterate('a 💩'[Symbol.iterator]()), ['a', ' ', '💩']);
5+
assert.deepEqual(iterate([1, 2][Symbol.iterator]()), [1, 2]);
6+
assert.deepEqual(iterate(new Set([1, 2]).values()), [1, 2]);
7+
assert.deepEqual(iterate(new Map([[1, 2], [3, 4]]).entries()), [[1, 2], [3, 4]]);
8+
9+
const foo = {
10+
count: 0,
11+
next() {
12+
if (this.count < 5) {
13+
this.count++;
14+
return { done: false, value: 42 };
15+
} else {
16+
return { done: true };
17+
}
18+
}
19+
};
20+
assert.deepStrictEqual(iterate(foo), [42, 42, 42, 42, 42]);
21+
22+
function assertWithCallback(iterable, expected) {
23+
const values = [];
24+
const callback = function (x) { values.push(x); };
25+
26+
iterate(iterable, callback);
27+
28+
assert.deepEqual(values, expected);
29+
}
30+
31+
assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
32+
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
33+
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
34+
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);
35+
36+
iterate([1, 2][Symbol.iterator](), function (x) { console.log(x); });
37+
38+
iterate([1, 2][Symbol.iterator](), x => console.log(x));

0 commit comments

Comments
 (0)