Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions codemods/iterate-iterator/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import jscodeshift from 'jscodeshift';
import { removeImport } from '../shared.js';

/**
* @typedef {import('../../types.js').Codemod} Codemod
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
*/

/**
* @param {CodemodOptions} [options]
* @returns {Codemod}
*/
export default function (options) {
return {
name: 'iterate-iterator',
transform: ({ file }) => {
const j = jscodeshift;
const root = j(file.source);
let isDirty = false;

const { identifier } = removeImport('iterate-iterator', root, j);

if (identifier) {
const callExpressions = root.find(j.CallExpression, {
callee: { type: 'Identifier', name: identifier },
});

for (const path of callExpressions.paths()) {
const args = path.node.arguments;
if (args.length === 1) {
// Case: Converting an iterator to an array
const [iterable] = args;
const iterableArg =
iterable.type === 'SpreadElement' ? iterable.argument : iterable;

const wrappedIterable = j.objectExpression([
j.property(
'init',
j.memberExpression(
j.identifier('Symbol'),
j.identifier('iterator'),
),
j.arrowFunctionExpression([], iterableArg),
),
]);

if (
wrappedIterable.properties[0].type !== 'SpreadProperty' &&
wrappedIterable.properties[0].type !== 'SpreadElement'
) {
wrappedIterable.properties[0].computed = true;
}

const arrayFromExpression = j.callExpression(
j.memberExpression(j.identifier('Array'), j.identifier('from')),
[wrappedIterable],
);
j(path).replaceWith(arrayFromExpression);
isDirty = true;
} else if (args.length === 2) {
// Case: Using a callback function
const [iterable, callback] = args;
const iterableArg =
iterable.type === 'SpreadElement' ? iterable.argument : iterable;

if (
callback.type !== 'Identifier' &&
callback.type !== 'FunctionExpression' &&
callback.type !== 'ArrowFunctionExpression'
) {
continue;
}

const wrappedIterable = j.objectExpression([
j.property(
'init',
j.memberExpression(
j.identifier('Symbol'),
j.identifier('iterator'),
),
j.arrowFunctionExpression([], iterableArg),
),
]);

if (
wrappedIterable.properties[0].type !== 'SpreadProperty' &&
wrappedIterable.properties[0].type !== 'SpreadElement'
) {
wrappedIterable.properties[0].computed = true;
}

const forOfStatement = j.forOfStatement(
j.variableDeclaration('const', [
j.variableDeclarator(j.identifier('i')),
]),
wrappedIterable,
j.blockStatement([
j.expressionStatement(
j.callExpression(
callback.type === 'Identifier'
? callback
: j.parenthesizedExpression(callback),
[j.identifier('i')],
),
),
]),
);
j(path).replaceWith(forOfStatement);
isDirty = true;
}
}
}

return isDirty ? root.toSource(options) : file.source;
},
};
}
59 changes: 59 additions & 0 deletions test/fixtures/iterate-iterator/case-1/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import assert from 'assert';

assert.deepEqual(Array.from({
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
}), ['a', ' ', '💩']);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Set([1, 2]).values()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
}), [[1, 2], [3, 4]]);

const foo = {
count: 0,
next() {
if (this.count < 5) {
this.count++;
return { done: false, value: 42 };
} else {
return { done: true };
}
}
};
assert.deepStrictEqual(Array.from({
[Symbol.iterator]: () => foo
}), [42, 42, 42, 42, 42]);

function assertWithCallback(iterable, expected) {
const values = [];
const callback = function (x) { values.push(x); };

for (const i of {
[Symbol.iterator]: () => iterable
}) {
callback(i);
};

assert.deepEqual(values, expected);
}

assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(function (x) { console.log(x); })(i);
};

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(x => console.log(x))(i);
};
38 changes: 38 additions & 0 deletions test/fixtures/iterate-iterator/case-1/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import iterateIterator from 'iterate-iterator';
import assert from 'assert';

assert.deepEqual(iterateIterator('a 💩'[Symbol.iterator]()), ['a', ' ', '💩']);
assert.deepEqual(iterateIterator([1, 2][Symbol.iterator]()), [1, 2]);
assert.deepEqual(iterateIterator(new Set([1, 2]).values()), [1, 2]);
assert.deepEqual(iterateIterator(new Map([[1, 2], [3, 4]]).entries()), [[1, 2], [3, 4]]);

const foo = {
count: 0,
next() {
if (this.count < 5) {
this.count++;
return { done: false, value: 42 };
} else {
return { done: true };
}
}
};
assert.deepStrictEqual(iterateIterator(foo), [42, 42, 42, 42, 42]);

function assertWithCallback(iterable, expected) {
const values = [];
const callback = function (x) { values.push(x); };

iterateIterator(iterable, callback);

assert.deepEqual(values, expected);
}

assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);

iterateIterator([1, 2][Symbol.iterator](), function (x) { console.log(x); });

iterateIterator([1, 2][Symbol.iterator](), x => console.log(x));
59 changes: 59 additions & 0 deletions test/fixtures/iterate-iterator/case-1/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import assert from 'assert';

assert.deepEqual(Array.from({
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
}), ['a', ' ', '💩']);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Set([1, 2]).values()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
}), [[1, 2], [3, 4]]);

const foo = {
count: 0,
next() {
if (this.count < 5) {
this.count++;
return { done: false, value: 42 };
} else {
return { done: true };
}
}
};
assert.deepStrictEqual(Array.from({
[Symbol.iterator]: () => foo
}), [42, 42, 42, 42, 42]);

function assertWithCallback(iterable, expected) {
const values = [];
const callback = function (x) { values.push(x); };

for (const i of {
[Symbol.iterator]: () => iterable
}) {
callback(i);
};

assert.deepEqual(values, expected);
}

assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(function (x) { console.log(x); })(i);
};

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(x => console.log(x))(i);
};
59 changes: 59 additions & 0 deletions test/fixtures/iterate-iterator/case-2/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const assert = require('assert');

assert.deepEqual(Array.from({
[Symbol.iterator]: () => 'a 💩'[Symbol.iterator]()
}), ['a', ' ', '💩']);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Set([1, 2]).values()
}), [1, 2]);
assert.deepEqual(Array.from({
[Symbol.iterator]: () => new Map([[1, 2], [3, 4]]).entries()
}), [[1, 2], [3, 4]]);

const foo = {
count: 0,
next() {
if (this.count < 5) {
this.count++;
return { done: false, value: 42 };
} else {
return { done: true };
}
}
};
assert.deepStrictEqual(Array.from({
[Symbol.iterator]: () => foo
}), [42, 42, 42, 42, 42]);

function assertWithCallback(iterable, expected) {
const values = [];
const callback = function (x) { values.push(x); };

for (const i of {
[Symbol.iterator]: () => iterable
}) {
callback(i);
};

assert.deepEqual(values, expected);
}

assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(function (x) { console.log(x); })(i);
};

for (const i of {
[Symbol.iterator]: () => [1, 2][Symbol.iterator]()
}) {
(x => console.log(x))(i);
};
38 changes: 38 additions & 0 deletions test/fixtures/iterate-iterator/case-2/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const assert = require('assert');
const iterate = require('iterate-iterator');

assert.deepEqual(iterate('a 💩'[Symbol.iterator]()), ['a', ' ', '💩']);
assert.deepEqual(iterate([1, 2][Symbol.iterator]()), [1, 2]);
assert.deepEqual(iterate(new Set([1, 2]).values()), [1, 2]);
assert.deepEqual(iterate(new Map([[1, 2], [3, 4]]).entries()), [[1, 2], [3, 4]]);

const foo = {
count: 0,
next() {
if (this.count < 5) {
this.count++;
return { done: false, value: 42 };
} else {
return { done: true };
}
}
};
assert.deepStrictEqual(iterate(foo), [42, 42, 42, 42, 42]);

function assertWithCallback(iterable, expected) {
const values = [];
const callback = function (x) { values.push(x); };

iterate(iterable, callback);

assert.deepEqual(values, expected);
}

assertWithCallback('a 💩'[Symbol.iterator](), ['a', ' ', '💩']);
assertWithCallback([1, 2][Symbol.iterator](), [1, 2]);
assertWithCallback(new Set([1, 2]).values(), [1, 2]);
assertWithCallback(new Map([[1, 2], [3, 4]]).entries(), [[1, 2], [3, 4]]);

iterate([1, 2][Symbol.iterator](), function (x) { console.log(x); });

iterate([1, 2][Symbol.iterator](), x => console.log(x));
Loading