{"size": 2718, "ext": "js", "max_stars_repo_path": "lib/index.js", "max_stars_repo_name": "jsbin/loop-protect", "max_stars_repo_stars_event_min_datetime": "2015-01-07T14:44:06.000Z", "max_stars_repo_stars_event_max_datetime": "2022-02-02T21:35:11.000Z", "max_issues_repo_path": "lib/index.js", "max_issues_repo_name": "jean-emmanuel/loop-protect", "max_issues_repo_issues_event_min_datetime": "2015-11-10T10:15:27.000Z", "max_issues_repo_issues_event_max_datetime": "2020-05-20T11:50:10.000Z", "max_forks_repo_path": "lib/index.js", "max_forks_repo_name": "jean-emmanuel/loop-protect", "max_forks_repo_forks_event_min_datetime": "2015-04-18T07:58:52.000Z", "max_forks_repo_forks_event_max_datetime": "2022-03-17T07:34:48.000Z"}
{"max_stars_count": 123, "max_issues_count": 11, "max_forks_count": 41, "avg_line_length": 26.9108910891, "max_line_length": 72, "alphanum_fraction": 0.5754231052}
const generateBefore = (t, id) =>
  t.variableDeclaration('var', [
    t.variableDeclarator(
      id,
      t.callExpression(
        t.memberExpression(t.identifier('Date'), t.identifier('now')),
        []
      )
    ),
  ]);

const generateInside = ({ t, id, line, ch, timeout, extra } = {}) => {
  return t.ifStatement(
    t.binaryExpression(
      '>',
      t.binaryExpression(
        '-',
        t.callExpression(
          t.memberExpression(t.identifier('Date'), t.identifier('now')),
          []
        ),
        id
      ),
      t.numericLiteral(timeout)
    ),
    extra
      ? t.blockStatement([
          t.expressionStatement(
            t.callExpression(extra, [
              t.numericLiteral(line),
              t.numericLiteral(ch),
            ])
          ),
          t.breakStatement(),
        ])
      : t.breakStatement()
  );
};

const protect = (t, timeout, extra) => path => {
  if (!path.node.loc) {
    // I don't really know _how_ we get into this state
    // but https://jsbin.com/mipesawapi/1/ triggers it
    // and the node, I'm guessing after translation,
    // doesn't have a line in the code, so this blows up.
    return;
  }
  const id = path.scope.generateUidIdentifier('LP');
  const before = generateBefore(t, id);
  const inside = generateInside({
    t,
    id,
    line: path.node.loc.start.line,
    ch: path.node.loc.start.column,
    timeout,
    extra,
  });
  const body = path.get('body');

  // if we have an expression statement, convert it to a block
  if (!t.isBlockStatement(body)) {
    body.replaceWith(t.blockStatement([body.node]));
  }
  path.insertBefore(before);
  body.unshiftContainer('body', inside);
};

module.exports = (timeout = 100, extra = null) => {
  if (typeof extra === 'string') {
    const string = extra;
    extra = `() => console.error("${string.replace(/"/g, '\\"')}")`;
  } else if (extra !== null) {
    extra = extra.toString();
    if (extra.startsWith('function (')) {
      // fix anonymous functions as they'll cause
      // the callback transform to blow up
      extra = extra.replace(/^function \(/, 'function callback(');
    }
  }

  return ({ types: t, transform }) => {
    const node = extra ?
      transform(extra, { ast: true }).ast.program.body[0] : null;

    let callback = null;
    if (t.isExpressionStatement(node)) {
      callback = node.expression;
    } else if (t.isFunctionDeclaration(node)) {
      callback = t.functionExpression(null, node.params, node.body);
    }

    return {
      visitor: {
        WhileStatement: protect(t, timeout, callback),
        ForStatement: protect(t, timeout, callback),
        DoWhileStatement: protect(t, timeout, callback),
      },
    };
  };
};
