Files
zulip/static/js/unread_ops.js
Anders Kaseorg 02511bff1c js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.

import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";

const checkExpression = (node: n.Node): node is K.ExpressionKind =>
  n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
  n.Statement.check(node);

for (const file of process.argv.slice(2)) {
  console.log("Parsing", file);
  const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
    parser: path.extname(file) === ".ts" ? tsParser : babelParser,
  });
  let changed = false;
  let inLoop = false;
  let replaceReturn = false;

  const visitLoop = (...args: string[]) =>
    function(this: Context, path: NodePath) {
      for (const arg of args) {
        this.visit(path.get(arg));
      }
      const old = { inLoop };
      inLoop = true;
      this.visit(path.get("body"));
      inLoop = old.inLoop;
      return false;
    };

  recast.visit(ast, {
    visitDoWhileStatement: visitLoop("test"),

    visitExpressionStatement(path) {
      const { expression, comments } = path.node;
      let valueOnly;
      if (
        n.CallExpression.check(expression) &&
        n.MemberExpression.check(expression.callee) &&
        !expression.callee.computed &&
        n.Identifier.check(expression.callee.object) &&
        expression.callee.object.name === "_" &&
        n.Identifier.check(expression.callee.property) &&
        ["each", "forEach"].includes(expression.callee.property.name) &&
        [2, 3].includes(expression.arguments.length) &&
        checkExpression(expression.arguments[0]) &&
        (n.FunctionExpression.check(expression.arguments[1]) ||
          n.ArrowFunctionExpression.check(expression.arguments[1])) &&
        [1, 2].includes(expression.arguments[1].params.length) &&
        n.Identifier.check(expression.arguments[1].params[0]) &&
        ((valueOnly = expression.arguments[1].params[1] === undefined) ||
          n.Identifier.check(expression.arguments[1].params[1])) &&
        (expression.arguments[2] === undefined ||
          n.ThisExpression.check(expression.arguments[2]))
      ) {
        const old = { inLoop, replaceReturn };
        inLoop = false;
        replaceReturn = true;
        this.visit(
          path
            .get("expression")
            .get("arguments")
            .get(1)
            .get("body")
        );
        inLoop = old.inLoop;
        replaceReturn = old.replaceReturn;

        const [right, { body, params }] = expression.arguments;
        const loop = b.forOfStatement(
          b.variableDeclaration("let", [
            b.variableDeclarator(
              valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
            ),
          ]),
          valueOnly
            ? right
            : b.callExpression(
                b.memberExpression(right, b.identifier("entries")),
                []
              ),
          checkStatement(body) ? body : b.expressionStatement(body)
        );
        loop.comments = comments;
        path.replace(loop);
        changed = true;
      }
      this.traverse(path);
    },

    visitForStatement: visitLoop("init", "test", "update"),

    visitForInStatement: visitLoop("left", "right"),

    visitForOfStatement: visitLoop("left", "right"),

    visitFunction(path) {
      this.visit(path.get("params"));
      const old = { replaceReturn };
      replaceReturn = false;
      this.visit(path.get("body"));
      replaceReturn = old.replaceReturn;
      return false;
    },

    visitReturnStatement(path) {
      if (replaceReturn) {
        assert(!inLoop); // could use labeled continue if this ever fires
        const { argument, comments } = path.node;
        if (argument === null) {
          const s = b.continueStatement();
          s.comments = comments;
          path.replace(s);
        } else {
          const s = b.expressionStatement(argument);
          s.comments = comments;
          path.replace(s, b.continueStatement());
        }
        return false;
      }
      this.traverse(path);
    },

    visitWhileStatement: visitLoop("test"),
  });

  if (changed) {
    console.log("Writing", file);
    fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
  }
}

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-07 14:09:47 -08:00

119 lines
3.3 KiB
JavaScript

exports.mark_all_as_read = function (cont) {
unread.declare_bankruptcy();
unread_ui.update_unread_counts();
channel.post({
url: '/json/mark_all_as_read',
idempotent: true,
success: cont});
};
function process_newly_read_message(message, options) {
home_msg_list.show_message_as_read(message, options);
message_list.all.show_message_as_read(message, options);
if (message_list.narrowed) {
message_list.narrowed.show_message_as_read(message, options);
}
notifications.close_notification(message);
}
exports.process_read_messages_event = function (message_ids) {
/*
This code has a lot in common with notify_server_messages_read,
but there are subtle differences due to the fact that the
server can tell us about unread messages that we didn't
actually read locally (and which we may not have even
loaded locally).
*/
const options = {from: 'server'};
message_ids = unread.get_unread_message_ids(message_ids);
if (message_ids.length === 0) {
return;
}
for (const message_id of message_ids) {
if (current_msg_list === message_list.narrowed) {
// I'm not sure this entirely makes sense for all server
// notifications.
unread.set_messages_read_in_narrow(true);
}
unread.mark_as_read(message_id);
const message = message_store.get(message_id);
if (message) {
process_newly_read_message(message, options);
}
}
unread_ui.update_unread_counts();
};
// Takes a list of messages and marks them as read.
// Skips any messages that are already marked as read.
exports.notify_server_messages_read = function (messages, options) {
options = options || {};
messages = unread.get_unread_messages(messages);
if (messages.length === 0) {
return;
}
message_flags.send_read(messages);
for (const message of messages) {
if (current_msg_list === message_list.narrowed) {
unread.set_messages_read_in_narrow(true);
}
unread.mark_as_read(message.id);
process_newly_read_message(message, options);
}
unread_ui.update_unread_counts();
};
exports.notify_server_message_read = function (message, options) {
exports.notify_server_messages_read([message], options);
};
// If we ever materially change the algorithm for this function, we
// may need to update notifications.received_messages as well.
exports.process_visible = function () {
if (!notifications.window_has_focus()) {
return;
}
if (message_viewport.bottom_message_visible() &&
current_msg_list.can_mark_messages_read()) {
exports.mark_current_list_as_read();
}
};
exports.mark_current_list_as_read = function (options) {
exports.notify_server_messages_read(current_msg_list.all_messages(), options);
};
exports.mark_stream_as_read = function (stream_id, cont) {
channel.post({
url: '/json/mark_stream_as_read',
idempotent: true,
data: {stream_id: stream_id},
success: cont,
});
};
exports.mark_topic_as_read = function (stream_id, topic, cont) {
channel.post({
url: '/json/mark_topic_as_read',
idempotent: true,
data: {stream_id: stream_id, topic_name: topic},
success: cont,
});
};
window.unread_ops = exports;