# Global Metrics

path: .metrics.halstead.volume
old: 1124.7323510644935
new: 4963.955310300051
path: .metrics.halstead.N2
old: 66.0
new: 298.0
path: .metrics.halstead.difficulty
old: 16.076923076923077
new: 43.8235294117647
path: .metrics.halstead.purity_ratio
old: 1.4939651525575826
new: 0.9028615222897168
path: .metrics.halstead.time
old: 1004.568638343928
new: 12085.446752201104
path: .metrics.halstead.effort
old: 18082.235490190706
new: 217538.04153961985
path: .metrics.halstead.level
old: 0.06220095693779904
new: 0.022818791946308727
path: .metrics.halstead.n1
old: 19.0
new: 25.0
path: .metrics.halstead.n2
old: 39.0
new: 85.0
path: .metrics.halstead.N1
old: 126.0
new: 434.0
path: .metrics.halstead.bugs
old: 0.2296396233479145
new: 1.2056897111309235
path: .metrics.halstead.estimated_program_length
old: 286.84130929105584
new: 660.8946343160727
path: .metrics.halstead.length
old: 192.0
new: 732.0
path: .metrics.halstead.vocabulary
old: 58.0
new: 110.0
path: .metrics.mi.mi_sei
old: 43.85170420969874
new: 0.1975780724226368
path: .metrics.mi.mi_original
old: 65.04965547638089
new: 24.11929997220173
path: .metrics.mi.mi_visual_studio
old: 38.04073419671397
new: 14.104853784913292
path: .metrics.cognitive.sum
old: 2.0
new: 8.0
path: .metrics.cognitive.average
old: 0.25
new: 0.26666666666666666
path: .metrics.loc.blank
old: 8.0
new: 38.0
path: .metrics.loc.lloc
old: 14.0
new: 55.0
path: .metrics.loc.cloc
old: 7.0
new: 85.0
path: .metrics.loc.ploc
old: 48.0
new: 179.0
path: .metrics.loc.sloc
old: 63.0
new: 302.0
path: .metrics.cyclomatic.sum
old: 10.0
new: 44.0
path: .metrics.cyclomatic.average
old: 1.25
new: 1.2571428571428571
path: .metrics.nexits.average
old: 0.875
new: 0.4
path: .metrics.nexits.sum
old: 7.0
new: 12.0
path: .metrics.nom.functions
old: 6.0
new: 28.0
path: .metrics.nom.total
old: 8.0
new: 30.0
path: .metrics.nargs.average
old: 1.625
new: 0.5666666666666667
path: .metrics.nargs.sum
old: 13.0
new: 17.0

# Spaces Data

## Minimal test - lines (61, 300)

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.2647058823529411
path: .spaces[0].metrics.cyclomatic.sum
old: 5.0
new: 43.0
path: .spaces[0].metrics.mi.mi_original
old: 84.51884826167563
new: 28.23481217871415
path: .spaces[0].metrics.mi.mi_sei
old: 46.74317055377577
new: -1.0320186661940127
path: .spaces[0].metrics.mi.mi_visual_studio
old: 49.42622705361148
new: 16.51158606942348
path: .spaces[0].metrics.nargs.average
old: 3.0
new: 0.5666666666666667
path: .spaces[0].metrics.nargs.sum
old: 12.0
new: 17.0
path: .spaces[0].metrics.nom.total
old: 4.0
new: 30.0
path: .spaces[0].metrics.nom.closures
old: 0.0
new: 2.0
path: .spaces[0].metrics.nom.functions
old: 4.0
new: 28.0
path: .spaces[0].metrics.halstead.length
old: 93.0
new: 721.0
path: .spaces[0].metrics.halstead.n2
old: 21.0
new: 77.0
path: .spaces[0].metrics.halstead.time
old: 196.21031746031747
new: 12539.020527428376
path: .spaces[0].metrics.halstead.purity_ratio
old: 1.4009936955200455
new: 0.8302898313146678
path: .spaces[0].metrics.halstead.difficulty
old: 7.595238095238095
new: 46.91558441558441
path: .spaces[0].metrics.halstead.N1
old: 64.0
new: 432.0
path: .spaces[0].metrics.halstead.effort
old: 3531.785714285714
new: 225702.3694937108
path: .spaces[0].metrics.halstead.n1
old: 11.0
new: 25.0
path: .spaces[0].metrics.halstead.vocabulary
old: 32.0
new: 102.0
path: .spaces[0].metrics.halstead.volume
old: 465.0
new: 4810.818671561448
path: .spaces[0].metrics.halstead.bugs
old: 0.07730512895798068
new: 1.2356709144394495
path: .spaces[0].metrics.halstead.N2
old: 29.0
new: 289.0
path: .spaces[0].metrics.halstead.estimated_program_length
old: 130.29241368336423
new: 598.6389683778755
path: .spaces[0].metrics.halstead.level
old: 0.1316614420062696
new: 0.021314878892733567
path: .spaces[0].metrics.cognitive.average
old: 0.0
new: 0.26666666666666666
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 8.0
path: .spaces[0].metrics.loc.blank
old: 2.0
new: 34.0
path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 40.0
path: .spaces[0].metrics.loc.lloc
old: 3.0
new: 55.0
path: .spaces[0].metrics.loc.ploc
old: 25.0
new: 166.0
path: .spaces[0].metrics.loc.sloc
old: 27.0
new: 240.0
path: .spaces[0].metrics.nexits.average
old: 0.75
new: 0.4
path: .spaces[0].metrics.nexits.sum
old: 3.0
new: 12.0

### Code

    namespace mozilla {

    extern LazyLogModule gStateWatchingLog;

    #  define WATCH_LOG(x, ...) \
        MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))

    /*
     * AbstractWatcher is a superclass from which all watchers must inherit.
     */
    class AbstractWatcher {
     public:
      NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
      AbstractWatcher() : mDestroyed(false) {}
      bool IsDestroyed() { return mDestroyed; }
      virtual void Notify() = 0;

     protected:
      virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
      bool mDestroyed;
    };

    /*
     * WatchTarget is a superclass from which all watchable things must inherit.
     * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
     * needs only to invoke NotifyWatchers when something changes.
     *
     * The functionality that this class provides is not threadsafe, and should only
     * be used on the thread that owns that WatchTarget.
     */
    class WatchTarget {
     public:
      explicit WatchTarget(const char* aName) : mName(aName) {}

      void AddWatcher(AbstractWatcher* aWatcher) {
        MOZ_ASSERT(!mWatchers.Contains(aWatcher));
        mWatchers.AppendElement(aWatcher);
      }

      void RemoveWatcher(AbstractWatcher* aWatcher) {
        MOZ_ASSERT(mWatchers.Contains(aWatcher));
        mWatchers.RemoveElement(aWatcher);
      }

     protected:
      void NotifyWatchers() {
        WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
        PruneWatchers();
        for (size_t i = 0; i < mWatchers.Length(); ++i) {
          mWatchers[i]->Notify();
        }
      }

     private:
      // We don't have Watchers explicitly unregister themselves when they die,
      // because then they'd need back-references to all the WatchTargets they're
      // subscribed to, and WatchTargets aren't reference-counted. So instead we
      // just prune dead ones at appropriate times, which works just fine.
      void PruneWatchers() {
        mWatchers.RemoveElementsBy(
            [](const auto& watcher) { return watcher->IsDestroyed(); });
      }

      nsTArray> mWatchers;

     protected:
      const char* mName;
    };

    /*
     * Watchable is a wrapper class that turns any primitive into a WatchTarget.
     */
    template
    class Watchable : public WatchTarget {
     public:
      Watchable(const T& aInitialValue, const char* aName)
          : WatchTarget(aName), mValue(aInitialValue) {}

      const T& Ref() const { return mValue; }
      operator const T&() const { return Ref(); }
      Watchable& operator=(const T& aNewValue) {
        if (aNewValue != mValue) {
          mValue = aNewValue;
          NotifyWatchers();
        }

        return *this;
      }

     private:
      Watchable(const Watchable& aOther) = delete;
      Watchable& operator=(const Watchable& aOther) = delete;

      T mValue;
    };

    // Manager class for state-watching. Declare one of these in any class for which
    // you want to invoke method callbacks.
    //
    // Internally, WatchManager maintains one AbstractWatcher per callback method.
    // Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
    // This causes an AbstractWatcher for |Callback| to be instantiated if it
    // doesn't already exist, and registers it with |WatchTarget|.
    //
    // Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
    // watch callbacks no more than once per task, once all other operations for
    // that task have been completed.
    //
    // WatchManager is intended to be declared as a member of |OwnerType|
    // objects. Given that, it and its owned objects can't hold permanent strong
    // refs to the owner, since that would keep the owner alive indefinitely.
    // Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
    // This ensures that everything is kept alive just long enough.
    template
    class WatchManager {
     public:
      typedef void (OwnerType::*CallbackMethod)();
      explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
          : mOwner(aOwner), mOwnerThread(aOwnerThread) {}

      ~WatchManager() {
        if (!IsShutdown()) {
          Shutdown();
        }
      }

      bool IsShutdown() const { return !mOwner; }

      // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
      // destroyed on a different thread, Shutdown() must be called manually.
      void Shutdown() {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        for (auto& watcher : mWatchers) {
          watcher->Destroy();
        }
        mWatchers.Clear();
        mOwner = nullptr;
      }

      void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        aTarget.AddWatcher(&EnsureWatcher(aMethod));
      }

      void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        MOZ_ASSERT(watcher);
        aTarget.RemoveWatcher(watcher);
      }

      void ManualNotify(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        MOZ_ASSERT(watcher);
        watcher->Notify();
      }

     private:
      class PerCallbackWatcher : public AbstractWatcher {
       public:
        PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
                           CallbackMethod aMethod)
            : mOwner(aOwner),
              mOwnerThread(aOwnerThread),
              mCallbackMethod(aMethod) {}

        void Destroy() {
          MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
          mDestroyed = true;
          mOwner = nullptr;
        }

        void Notify() override {
          MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
          MOZ_DIAGNOSTIC_ASSERT(mOwner,
                                "mOwner is only null after destruction, "
                                "at which point we shouldn't be notified");
          if (mNotificationPending) {
            // We've already got a notification job in the pipe.
            return;
          }
          mNotificationPending = true;

          // Queue up our notification jobs to run in a stable state.
          AbstractThread::DispatchDirectTask(
              NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
                                     [self = RefPtr(this),
                                      owner = RefPtr(mOwner)]() {
                                       if (!self->mDestroyed) {
                                         ((*owner).*(self->mCallbackMethod))();
                                       }
                                       self->mNotificationPending = false;
                                     }));
        }

        bool CallbackMethodIs(CallbackMethod aMethod) const {
          return mCallbackMethod == aMethod;
        }

       private:
        ~PerCallbackWatcher() = default;

        OwnerType* mOwner;  // Never null.
        bool mNotificationPending = false;
        RefPtr mOwnerThread;
        CallbackMethod mCallbackMethod;
      };

      PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        for (auto& watcher : mWatchers) {
          if (watcher->CallbackMethodIs(aMethod)) {
            return watcher;
          }
        }
        return nullptr;
      }

      PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        if (watcher) {
          return *watcher;
        }
        watcher = mWatchers
                      .AppendElement(MakeAndAddRef(
                          mOwner, mOwnerThread, aMethod))
                      ->get();
        return *watcher;
      }

      nsTArray> mWatchers;
      OwnerType* mOwner;
      RefPtr mOwnerThread;
    };

    #  undef WATCH_LOG

    }  // namespace mozilla

## Minimal test - lines (71, 81)

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 106.6746999031816
new: 104.97484534296882
path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: 78.30002840603022
new: 76.15311623999212
path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 62.38286544045708
new: 61.388798446180594
path: .spaces[0].spaces[0].metrics.nexits.average
old: 1.0
new: 0.3333333333333333
path: .spaces[0].spaces[0].metrics.nargs.sum
old: 9.0
new: 0.0
path: .spaces[0].spaces[0].metrics.nargs.average
old: 9.0
new: 0.0
path: .spaces[0].spaces[0].metrics.loc.blank
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.loc.lloc
old: 1.0
new: 2.0
path: .spaces[0].spaces[0].metrics.loc.sloc
old: 10.0
new: 11.0
path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 4.0
path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.25
new: 0.14285714285714285
path: .spaces[0].spaces[0].metrics.halstead.N2
old: 12.0
new: 14.0
path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.026066162105227123
new: 0.03534764658819855
path: .spaces[0].spaces[0].metrics.halstead.effort
old: 691.508495181978
new: 1092.0
path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 67.01955000865388
new: 48.0
path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 20.0
new: 16.0
path: .spaces[0].spaces[0].metrics.halstead.n2
old: 12.0
new: 8.0
path: .spaces[0].spaces[0].metrics.halstead.N1
old: 28.0
new: 25.0
path: .spaces[0].spaces[0].metrics.halstead.volume
old: 172.8771237954945
new: 156.0
path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 4.0
new: 7.0
path: .spaces[0].spaces[0].metrics.halstead.length
old: 40.0
new: 39.0
path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.675488750216347
new: 1.2307692307692308
path: .spaces[0].spaces[0].metrics.halstead.time
old: 38.417138621221
new: 60.666666666666664
path: .spaces[0].spaces[0].metrics.nom.functions
old: 1.0
new: 3.0
path: .spaces[0].spaces[0].metrics.nom.total
old: 1.0
new: 3.0

### Code

    class AbstractWatcher {
     public:
      NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
      AbstractWatcher() : mDestroyed(false) {}
      bool IsDestroyed() { return mDestroyed; }
      virtual void Notify() = 0;

     protected:
      virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
      bool mDestroyed;
    };

## Minimal test - lines (175, 296)

path: .spaces[0].spaces[3].metrics.nexits.sum
old: 1.0
new: 7.0
path: .spaces[0].spaces[3].metrics.nexits.average
old: 1.0
new: 0.4666666666666667
path: .spaces[0].spaces[3].metrics.nom.total
old: 1.0
new: 15.0
path: .spaces[0].spaces[3].metrics.nom.closures
old: 0.0
new: 1.0
path: .spaces[0].spaces[3].metrics.nom.functions
old: 1.0
new: 14.0
path: .spaces[0].spaces[3].metrics.cyclomatic.average
old: 1.0
new: 1.4375
path: .spaces[0].spaces[3].metrics.cyclomatic.sum
old: 1.0
new: 23.0
path: .spaces[0].spaces[3].metrics.mi.mi_sei
old: 102.58917596407876
new: 9.3912101094776
path: .spaces[0].spaces[3].metrics.mi.mi_original
old: 123.51065405124744
new: 46.66596931013636
path: .spaces[0].spaces[3].metrics.mi.mi_visual_studio
old: 72.2284526615482
new: 27.290040532243484
path: .spaces[0].spaces[3].metrics.loc.ploc
old: 5.0
new: 102.0
path: .spaces[0].spaces[3].metrics.loc.lloc
old: 1.0
new: 37.0
path: .spaces[0].spaces[3].metrics.loc.blank
old: 0.0
new: 15.0
path: .spaces[0].spaces[3].metrics.loc.cloc
old: 0.0
new: 5.0
path: .spaces[0].spaces[3].metrics.loc.sloc
old: 5.0
new: 122.0
path: .spaces[0].spaces[3].metrics.cognitive.sum
old: 0.0
new: 6.0
path: .spaces[0].spaces[3].metrics.cognitive.average
old: 0.0
new: 0.4
path: .spaces[0].spaces[3].metrics.halstead.volume
old: 58.81033751683405
new: 2770.296325647598
path: .spaces[0].spaces[3].metrics.halstead.purity_ratio
old: 1.6265579090825428
new: 0.8469939979927766
path: .spaces[0].spaces[3].metrics.halstead.N2
old: 5.0
new: 186.0
path: .spaces[0].spaces[3].metrics.halstead.vocabulary
old: 11.0
new: 72.0
path: .spaces[0].spaces[3].metrics.halstead.difficulty
old: 4.375
new: 40.92
path: .spaces[0].spaces[3].metrics.halstead.length
old: 17.0
new: 449.0
path: .spaces[0].spaces[3].metrics.halstead.level
old: 0.22857142857142856
new: 0.02443792766373411
path: .spaces[0].spaces[3].metrics.halstead.n1
old: 7.0
new: 22.0
path: .spaces[0].spaces[3].metrics.halstead.N1
old: 12.0
new: 263.0
path: .spaces[0].spaces[3].metrics.halstead.bugs
old: 0.013484449818562038
new: 0.7807643576375038
path: .spaces[0].spaces[3].metrics.halstead.n2
old: 4.0
new: 50.0
path: .spaces[0].spaces[3].metrics.halstead.effort
old: 257.295226636149
new: 113360.52564549971
path: .spaces[0].spaces[3].metrics.halstead.estimated_program_length
old: 27.651484454403228
new: 380.3003050987567
path: .spaces[0].spaces[3].metrics.halstead.time
old: 14.294179257563831
new: 6297.8069803055405
path: .spaces[0].spaces[3].metrics.nargs.average
old: 0.0
new: 0.7333333333333333
path: .spaces[0].spaces[3].metrics.nargs.sum
old: 0.0
new: 11.0

### Code

    class WatchManager {
     public:
      typedef void (OwnerType::*CallbackMethod)();
      explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
          : mOwner(aOwner), mOwnerThread(aOwnerThread) {}

      ~WatchManager() {
        if (!IsShutdown()) {
          Shutdown();
        }
      }

      bool IsShutdown() const { return !mOwner; }

      // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
      // destroyed on a different thread, Shutdown() must be called manually.
      void Shutdown() {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        for (auto& watcher : mWatchers) {
          watcher->Destroy();
        }
        mWatchers.Clear();
        mOwner = nullptr;
      }

      void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        aTarget.AddWatcher(&EnsureWatcher(aMethod));
      }

      void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        MOZ_ASSERT(watcher);
        aTarget.RemoveWatcher(watcher);
      }

      void ManualNotify(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        MOZ_ASSERT(watcher);
        watcher->Notify();
      }

     private:
      class PerCallbackWatcher : public AbstractWatcher {
       public:
        PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
                           CallbackMethod aMethod)
            : mOwner(aOwner),
              mOwnerThread(aOwnerThread),
              mCallbackMethod(aMethod) {}

        void Destroy() {
          MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
          mDestroyed = true;
          mOwner = nullptr;
        }

        void Notify() override {
          MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
          MOZ_DIAGNOSTIC_ASSERT(mOwner,
                                "mOwner is only null after destruction, "
                                "at which point we shouldn't be notified");
          if (mNotificationPending) {
            // We've already got a notification job in the pipe.
            return;
          }
          mNotificationPending = true;

          // Queue up our notification jobs to run in a stable state.
          AbstractThread::DispatchDirectTask(
              NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
                                     [self = RefPtr(this),
                                      owner = RefPtr(mOwner)]() {
                                       if (!self->mDestroyed) {
                                         ((*owner).*(self->mCallbackMethod))();
                                       }
                                       self->mNotificationPending = false;
                                     }));
        }

        bool CallbackMethodIs(CallbackMethod aMethod) const {
          return mCallbackMethod == aMethod;
        }

       private:
        ~PerCallbackWatcher() = default;

        OwnerType* mOwner;  // Never null.
        bool mNotificationPending = false;
        RefPtr mOwnerThread;
        CallbackMethod mCallbackMethod;
      };

      PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        for (auto& watcher : mWatchers) {
          if (watcher->CallbackMethodIs(aMethod)) {
            return watcher;
          }
        }
        return nullptr;
      }

      PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
        MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
        PerCallbackWatcher* watcher = GetWatcher(aMethod);
        if (watcher) {
          return *watcher;
        }
        watcher = mWatchers
                      .AppendElement(MakeAndAddRef(
                          mOwner, mOwnerThread, aMethod))
                      ->get();
        return *watcher;
      }

      nsTArray> mWatchers;
      OwnerType* mOwner;
      RefPtr mOwnerThread;
    };

## Minimal test - lines (134, 155)

path: .spaces[0].spaces[2].metrics.nom.functions
old: 1.0
new: 6.0
path: .spaces[0].spaces[2].metrics.nom.total
old: 1.0
new: 6.0
path: .spaces[0].spaces[2].metrics.cognitive.average
old: 0.0
new: 0.16666666666666666
path: .spaces[0].spaces[2].metrics.cognitive.sum
old: 0.0
new: 1.0
path: .spaces[0].spaces[2].metrics.cyclomatic.average
old: 1.0
new: 1.1428571428571428
path: .spaces[0].spaces[2].metrics.cyclomatic.sum
old: 1.0
new: 8.0
path: .spaces[0].spaces[2].metrics.halstead.purity_ratio
old: 1.8823529411764703
new: 0.97981924439407
path: .spaces[0].spaces[2].metrics.halstead.n2
old: 4.0
new: 10.0
path: .spaces[0].spaces[2].metrics.halstead.effort
old: 304.72181256129824
new: 7565.43119340755
path: .spaces[0].spaces[2].metrics.halstead.bugs
old: 0.015094350855465146
new: 0.1284599856398078
path: .spaces[0].spaces[2].metrics.halstead.volume
old: 60.94436251225965
new: 375.45564235273207
path: .spaces[0].spaces[2].metrics.halstead.n1
old: 8.0
new: 13.0
path: .spaces[0].spaces[2].metrics.halstead.N1
old: 12.0
new: 52.0
path: .spaces[0].spaces[2].metrics.halstead.N2
old: 5.0
new: 31.0
path: .spaces[0].spaces[2].metrics.halstead.estimated_program_length
old: 32.0
new: 81.32499728470782
path: .spaces[0].spaces[2].metrics.halstead.length
old: 17.0
new: 83.0
path: .spaces[0].spaces[2].metrics.halstead.level
old: 0.2
new: 0.04962779156327544
path: .spaces[0].spaces[2].metrics.halstead.difficulty
old: 5.0
new: 20.15
path: .spaces[0].spaces[2].metrics.halstead.time
old: 16.92898958673879
new: 420.30173296708614
path: .spaces[0].spaces[2].metrics.halstead.vocabulary
old: 12.0
new: 23.0
path: .spaces[0].spaces[2].metrics.loc.blank
old: 0.0
new: 4.0
path: .spaces[0].spaces[2].metrics.loc.sloc
old: 4.0
new: 22.0
path: .spaces[0].spaces[2].metrics.loc.ploc
old: 4.0
new: 18.0
path: .spaces[0].spaces[2].metrics.loc.lloc
old: 1.0
new: 6.0
path: .spaces[0].spaces[2].metrics.nargs.average
old: 0.0
new: 0.5
path: .spaces[0].spaces[2].metrics.nargs.sum
old: 0.0
new: 3.0
path: .spaces[0].spaces[2].metrics.nexits.average
old: 1.0
new: 0.5
path: .spaces[0].spaces[2].metrics.nexits.sum
old: 1.0
new: 3.0
path: .spaces[0].spaces[2].metrics.mi.mi_sei
old: 107.53701147893022
new: 52.444214732937326
path: .spaces[0].spaces[2].metrics.mi.mi_visual_studio
old: 74.23405396973165
new: 51.61332310835995
path: .spaces[0].spaces[2].metrics.mi.mi_original
old: 126.9402322882411
new: 88.25878251529551

### Code

    class Watchable : public WatchTarget {
     public:
      Watchable(const T& aInitialValue, const char* aName)
          : WatchTarget(aName), mValue(aInitialValue) {}

      const T& Ref() const { return mValue; }
      operator const T&() const { return Ref(); }
      Watchable& operator=(const T& aNewValue) {
        if (aNewValue != mValue) {
          mValue = aNewValue;
          NotifyWatchers();
        }

        return *this;
      }

     private:
      Watchable(const Watchable& aOther) = delete;
      Watchable& operator=(const Watchable& aOther) = delete;

      T mValue;
    };

## Minimal test - lines (91, 128)

path: .spaces[0].spaces[1].metrics.nom.total
old: 1.0
new: 6.0
path: .spaces[0].spaces[1].metrics.nom.closures
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.nom.functions
old: 1.0
new: 5.0
path: .spaces[0].spaces[1].metrics.nargs.average
old: 3.0
new: 0.5
path: .spaces[0].spaces[1].metrics.nexits.average
old: 0.0
new: 0.16666666666666666
path: .spaces[0].spaces[1].metrics.nexits.sum
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.cognitive.sum
old: 0.0
new: 1.0
path: .spaces[0].spaces[1].metrics.cognitive.average
old: 0.0
new: 0.16666666666666666
path: .spaces[0].spaces[1].metrics.cyclomatic.sum
old: 1.0
new: 7.0
path: .spaces[0].spaces[1].metrics.cyclomatic.average
old: 1.0
new: 1.1666666666666667
path: .spaces[0].spaces[1].metrics.halstead.difficulty
old: 3.0
new: 18.0
path: .spaces[0].spaces[1].metrics.halstead.N1
old: 10.0
new: 80.0
path: .spaces[0].spaces[1].metrics.halstead.level
old: 0.3333333333333333
new: 0.05555555555555555
path: .spaces[0].spaces[1].metrics.halstead.purity_ratio
old: 1.8079610319175832
new: 1.4704234982333058
path: .spaces[0].spaces[1].metrics.halstead.time
old: 8.648579046593243
new: 705.4144181112728
path: .spaces[0].spaces[1].metrics.halstead.estimated_program_length
old: 27.11941547876375
new: 191.15505477032977
path: .spaces[0].spaces[1].metrics.halstead.n2
old: 5.0
new: 25.0
path: .spaces[0].spaces[1].metrics.halstead.volume
old: 51.89147427955946
new: 705.4144181112728
path: .spaces[0].spaces[1].metrics.halstead.bugs
old: 0.009646214643915437
new: 0.18142200824967217
path: .spaces[0].spaces[1].metrics.halstead.N2
old: 5.0
new: 50.0
path: .spaces[0].spaces[1].metrics.halstead.n1
old: 6.0
new: 18.0
path: .spaces[0].spaces[1].metrics.halstead.effort
old: 155.67442283867837
new: 12697.45952600291
path: .spaces[0].spaces[1].metrics.halstead.vocabulary
old: 11.0
new: 43.0
path: .spaces[0].spaces[1].metrics.halstead.length
old: 15.0
new: 130.0
path: .spaces[0].spaces[1].metrics.mi.mi_sei
old: 108.74338677859146
new: 59.25583590014251
path: .spaces[0].spaces[1].metrics.mi.mi_original
old: 127.77642792589845
new: 76.35541983490009
path: .spaces[0].spaces[1].metrics.mi.mi_visual_studio
old: 74.72305726660727
new: 44.65229230111117
path: .spaces[0].spaces[1].metrics.loc.lloc
old: 0.0
new: 10.0
path: .spaces[0].spaces[1].metrics.loc.ploc
old: 4.0
new: 28.0
path: .spaces[0].spaces[1].metrics.loc.blank
old: 0.0
new: 6.0
path: .spaces[0].spaces[1].metrics.loc.cloc
old: 0.0
new: 4.0
path: .spaces[0].spaces[1].metrics.loc.sloc
old: 4.0
new: 38.0

### Code

    class WatchTarget {
     public:
      explicit WatchTarget(const char* aName) : mName(aName) {}

      void AddWatcher(AbstractWatcher* aWatcher) {
        MOZ_ASSERT(!mWatchers.Contains(aWatcher));
        mWatchers.AppendElement(aWatcher);
      }

      void RemoveWatcher(AbstractWatcher* aWatcher) {
        MOZ_ASSERT(mWatchers.Contains(aWatcher));
        mWatchers.RemoveElement(aWatcher);
      }

     protected:
      void NotifyWatchers() {
        WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
        PruneWatchers();
        for (size_t i = 0; i < mWatchers.Length(); ++i) {
          mWatchers[i]->Notify();
        }
      }

     private:
      // We don't have Watchers explicitly unregister themselves when they die,
      // because then they'd need back-references to all the WatchTargets they're
      // subscribed to, and WatchTargets aren't reference-counted. So instead we
      // just prune dead ones at appropriate times, which works just fine.
      void PruneWatchers() {
        mWatchers.RemoveElementsBy(
            [](const auto& watcher) { return watcher->IsDestroyed(); });
      }

      nsTArray> mWatchers;

     protected:
      const char* mName;
    };