/**
 * @fileoverview Reject use of XPCOMUtils.generateQI and JS-implemented
 *               QueryInterface methods in favor of ChromeUtils.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

function isIdentifier(node, id) {
  return node && node.type === "Identifier" && node.name === id;
}

function isMemberExpression(node, object, member) {
  return (
    node.type === "MemberExpression" &&
    isIdentifier(node.object, object) &&
    isIdentifier(node.property, member)
  );
}

const MSG_NO_JS_QUERY_INTERFACE =
  "Please use ChromeUtils.generateQI rather than manually creating " +
  "JavaScript QueryInterface functions";

const MSG_NO_XPCOMUTILS_GENERATEQI =
  "Please use ChromeUtils.generateQI instead of XPCOMUtils.generateQI";

function funcToGenerateQI(context, node) {
  const sourceCode = context.getSourceCode();
  const text = sourceCode.getText(node);

  let interfaces = [];
  let match;
  let re = /\bCi\.([a-zA-Z0-9]+)\b|\b(nsI[A-Z][a-zA-Z0-9]+)\b/g;
  while ((match = re.exec(text))) {
    interfaces.push(match[1] || match[2]);
  }

  let ifaces = interfaces
    .filter(iface => iface != "nsISupports")
    .map(iface => JSON.stringify(iface))
    .join(", ");

  return `ChromeUtils.generateQI([${ifaces}])`;
}

module.exports = {
  meta: {
    docs: {
      url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.html",
    },
    fixable: "code",
    schema: [],
    type: "suggestion",
  },

  create(context) {
    return {
      CallExpression(node) {
        let { callee } = node;
        if (isMemberExpression(callee, "XPCOMUtils", "generateQI")) {
          context.report({
            node,
            message: MSG_NO_XPCOMUTILS_GENERATEQI,
            fix(fixer) {
              return fixer.replaceText(callee, "ChromeUtils.generateQI");
            },
          });
        }
      },

      "AssignmentExpression > MemberExpression[property.name='QueryInterface']":
        function (node) {
          const { right } = node.parent;
          if (right.type === "FunctionExpression") {
            context.report({
              node: node.parent,
              message: MSG_NO_JS_QUERY_INTERFACE,
              fix(fixer) {
                return fixer.replaceText(
                  right,
                  funcToGenerateQI(context, right)
                );
              },
            });
          }
        },

      "Property[key.name='QueryInterface'][value.type='FunctionExpression']":
        function (node) {
          context.report({
            node,
            message: MSG_NO_JS_QUERY_INTERFACE,
            fix(fixer) {
              let generateQI = funcToGenerateQI(context, node.value);
              return fixer.replaceText(node, `QueryInterface: ${generateQI}`);
            },
          });
        },
    };
  },
};
