coderaiser / putout

🐊 Pluggable and configurable JavaScript Linter, code transformer and formatter, drop-in ESLint superpower replacement 💪 with built-in support for js, jsx, typescript, flow, markdown, yaml and json. Write declarative codemods in a simplest possible way 😏
https://putout.cloudcmd.io/
MIT License
699 stars 40 forks source link

how to remove "yield" #218

Closed retorquere closed 1 week ago

retorquere commented 1 week ago

when I try to create this plugin:

const unpromise = {
    report: () => 'async should be stripped',

    replace: () => ({
      'yield __a'({__a}, path) {
        replaceWith(path, ReturnStatement(__a))
        return path
      },
    }),
  }

I'm getting

SyntaxError: Unexpected reserved word 'yield'
retorquere commented 1 week ago

I'm trying to strip the generator bits away from

function internalUpsertMany(table, entities) {
    return _$awaiter(this, void 0, void 0, function*() {
        // Split entities into items to create & items to update
        // reduces the number of outgoing events to 2
        const items = [];
        const primaryKeyProperty = table[createDB_1.BlinkKey].options.primary;

        for (const entity of entities) {
            const primaryKey = entity[primaryKeyProperty];
            const primaryKeyExists = table[createDB_1.BlinkKey].storage.primary.has(primaryKey);
            items.push({
                entity,
                method: primaryKeyExists ? 'update' : 'insert',
            });
        }

        // Insert all items that need to be inserted, update all that need to be updated
        const itemsToInsert = items.filter((i) => i.method === 'insert');
        const itemsToUpdate = items.filter((i) => i.method === 'update');
        const insertIds = yield (0, insertMany_1.internalInsertMany)(table, itemsToInsert.map((i) => i.entity));
        const updateIds = yield (0, updateMany_1.internalUpdateMany)(table, itemsToUpdate.map((i) => i.entity));

        // Return the indexes in the correct order
        const ids = [];
        let insertIdsIndex = 0;
        let updateIdsIndex = 0;

        for (const item of items) {
            switch(item.method) {
            case 'insert':
                ids.push(insertIds[insertIdsIndex]);
                insertIdsIndex++;
                break;

            case 'update':
                ids.push(updateIds[updateIdsIndex]);
                updateIdsIndex++;
            }
        }

        return ids;
    });
}

so I want to remove both the yields and the function* wrapper.

coderaiser commented 1 week ago

The thing is YieldExpression can only exist inside Generator Function.

You can use Traverser to convert yield to return:

// convert-yield-to-return

export const report = () => `Use 'return' instead of 'yield'`;

export const fix = (path) => {
  if (isFunction(path))
    return path.node.generator = false;

  const {argument} = path.node;
  path.replaceWith(ReturnStatement(argument));
}

export const traverse = ({push}) => ({
  YieldExpression(path) {
    push(path);
  },
  Program: {
      exit(path) {
        path.traverse({
          Function(path) {
            push(path);
          }
        })
      }
  }
});

🐊Putout Editor

And redput to download code with tests and fixtures:

redput https://putout.vercel.app/#/gist/bb3fd2441f72e69a40efcb34e444a139/b33e387a809805928ed528ad545fb428a0211525
retorquere commented 1 week ago

I have tried to combine those as

const unyield = {
    report: () => `Use 'return' instead of 'yield'`,
    include: ['YieldExpression'],
    fix: (path) => {
      path.replaceWith(path.node.argument) // I want to just have the argument, not return it, I had this wrong earlier
    },
    traverse: ({push}) => ({
      YieldExpression(path) {
        push(path)
      },
      Program: {
        exit(path) {
          path.traverse({
            Function(path) {
              push(path);
            }
          })
        }
      }
    }),
  }

but when I use this plugin, I get

node_modules/@putout/engine-runner/lib/merge-visitors.js:27
        list.push(...maybeArray(plugin[name]()));
                                            ^

TypeError: plugin[name] is not a function
    at parse (node_modules/@putout/engine-runner/lib/merge-visitors.js:27:45)
coderaiser commented 1 week ago

There is a couple problems:

  1. You cannot mix Includer with Traverser since only one is used.
  2. include is a function, everything plugin for 🐊Putout exports is always a function, this case is handled by @putout/plugin-putout enable it in your .putout.json and it will autofix such code (bun only if you use exports).
retorquere commented 1 week ago

I'm trying to remove both the __awaiter wrapper (which will also remove the generator function) and the yield statements (leaving a simple assignment.

retorquere commented 1 week ago

I don't have it at all, sorry.

coderaiser commented 1 week ago

Could you please provide me examples what do you have now, and what do you want to achieve?

This should work as you want:

// convert-yield-to-return

export const report = () => `Use 'return' instead of 'yield'`;

export const fix = (path) => {
  if (isFunction(path))
    return path.node.generator = false;

  const {argument} = path.node;
  path.replaceWith(argument);
}

export const traverse = ({push}) => ({
  YieldExpression(path) {
    push(path);
  },
  Program: {
      exit(path) {
        path.traverse({
          Function(path) {
            push(path);
          }
        })
      }
  }
})
retorquere commented 1 week ago
What I have ```js function internalUpsertMany(table, entities) { return _$awaiter(this, void 0, void 0, function*() { // Split entities into items to create & items to update // reduces the number of outgoing events to 2 const items = []; const primaryKeyProperty = table[createDB_1.BlinkKey].options.primary; for (const entity of entities) { const primaryKey = entity[primaryKeyProperty]; const primaryKeyExists = table[createDB_1.BlinkKey].storage.primary.has(primaryKey); items.push({ entity, method: primaryKeyExists ? 'update' : 'insert', }); } // Insert all items that need to be inserted, update all that need to be updated const itemsToInsert = items.filter((i) => i.method === 'insert'); const itemsToUpdate = items.filter((i) => i.method === 'update'); const insertIds = yield (0, insertMany_1.internalInsertMany)(table, itemsToInsert.map((i) => i.entity)); const updateIds = yield (0, updateMany_1.internalUpdateMany)(table, itemsToUpdate.map((i) => i.entity)); // Return the indexes in the correct order const ids = []; let insertIdsIndex = 0; let updateIdsIndex = 0; for (const item of items) { switch(item.method) { case 'insert': ids.push(insertIds[insertIdsIndex]); insertIdsIndex++; break; case 'update': ids.push(updateIds[updateIdsIndex]); updateIdsIndex++; } } return ids; }); } ```
What I want: ```js function internalUpsertMany(table, entities) { // Split entities into items to create & items to update // reduces the number of outgoing events to 2 const items = []; const primaryKeyProperty = table[createDB_1.BlinkKey].options.primary; for (const entity of entities) { const primaryKey = entity[primaryKeyProperty]; const primaryKeyExists = table[createDB_1.BlinkKey].storage.primary.has(primaryKey); items.push({ entity, method: primaryKeyExists ? 'update' : 'insert', }); } // Insert all items that need to be inserted, update all that need to be updated const itemsToInsert = items.filter((i) => i.method === 'insert'); const itemsToUpdate = items.filter((i) => i.method === 'update'); const insertIds = (0, insertMany_1.internalInsertMany)(table, itemsToInsert.map((i) => i.entity)); const updateIds = (0, updateMany_1.internalUpdateMany)(table, itemsToUpdate.map((i) => i.entity)); // Return the indexes in the correct order const ids = []; let insertIdsIndex = 0; let updateIdsIndex = 0; for (const item of items) { switch(item.method) { case 'insert': ids.push(insertIds[insertIdsIndex]); insertIdsIndex++; break; case 'update': ids.push(updateIds[updateIdsIndex]); updateIdsIndex++; } } return ids; } ```
coderaiser commented 1 week ago

The best way would be to split transformations to two steps:

  1. Get rid of yield (in the previous comment);
  2. Get rid of _$awaiter Here is how Replacer can look like:

// remove-useless-awaiter

export const report = () => `Avoid '_$awaiter'`;

export const replace = () => ({
  '{return _$awaiter(__args)}': ({__args}) => {
    return __args.at(-1).body
  }
})

Is it works for you?

retorquere commented 1 week ago

Yes! Thank you!