| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 | /** * @fileoverview Check that there's a Services.(prefs|obs).removeObserver for * each addObserver. * * 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";module.exports = {  meta: {    docs: {      url:        "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.html",    },    type: "problem",  },  create(context) {    var addedObservers = [];    var removedObservers = [];    function getObserverAPI(node) {      const object = node.callee.object;      if (        object.type == "MemberExpression" &&        object.property.type == "Identifier"      ) {        return object.property.name;      }      return null;    }    function isServicesObserver(api) {      return api == "obs" || api == "prefs";    }    function getObservableName(node, api) {      if (api === "obs") {        return node.arguments[1].value;      }      return node.arguments[0].value;    }    function addAddedObserver(node) {      const api = getObserverAPI(node);      if (!isServicesObserver(api)) {        return;      }      addedObservers.push({        functionName: node.callee.property.name,        observable: getObservableName(node, api),        node: node.callee.property,      });    }    function addRemovedObserver(node) {      const api = getObserverAPI(node);      if (!isServicesObserver(api)) {        return;      }      removedObservers.push({        functionName: node.callee.property.name,        observable: getObservableName(node, api),      });    }    function getUnbalancedObservers() {      const unbalanced = addedObservers.filter(        observer => !hasRemovedObserver(observer)      );      addedObservers = removedObservers = [];      return unbalanced;    }    function hasRemovedObserver(addedObserver) {      return removedObservers.some(        observer => addedObserver.observable === observer.observable      );    }    return {      CallExpression(node) {        if (node.arguments.length === 0) {          return;        }        if (node.callee.type === "MemberExpression") {          var methodName = node.callee.property.name;          if (methodName === "addObserver") {            addAddedObserver(node);          } else if (methodName === "removeObserver") {            addRemovedObserver(node);          }        }      },      "Program:exit": function() {        getUnbalancedObservers().forEach(function(observer) {          context.report({            node: observer.node,            message:              "No corresponding 'removeObserver(\"{{observable}}\")' was found.",            data: {              observable: observer.observable,            },          });        });      },    };  },};
 |