<commit_before>(function() {

  var ThumbBox = function(options) {
    options = options || {};
    this.issue_url = options.issue_url;
    this.impression = options.impression;
    if (options.size === 'small') {
      this.container_class = 'bountysource-thumbs-box-mini';
      this.image_path = BountysourceClient.imagePath('thumbsup-20.png');
    } else {
      this.container_class = 'bountysource-thumbs-box';
      this.image_path = BountysourceClient.imagePath('thumbsup-32.png');
    }

    this.createDom();
    this.setInfoBox('spinner');
  };

  ThumbBox.loadAllData = function(instances, attempts) {
    var urls = [];
    for (var i=0; i < instances.length; i++) {
      urls.push(instances[i].issue_url);
    }

    if (urls.length === 0) {
      return;
    }

    attempts = (attempts || 0) + 1;
    BountysourceClient.api({
      method: 'POST',
      path: '/thumbs/index',
      body: {
        urls: urls,
        impression: instances[0].impression
      }
    }, function(response) {
      if (!response) {
        console.log("ERROR3", response);
        for (var i=0; i < instances.length; i++) {
          instances[i].setInfoBox('ERR3', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      } else if (response.length !== instances.length) {
        console.log("ERROR4, unexpected response", instances.length, response.length);
        for (var i=0; i < instances.length; i++) {
          instances[i].setInfoBox('ERR4', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      } else {
        var retry_instances = [];

        for (var i=0; i < response.length; i++) {
          if (response[i].issue_id) {
            instances[i].setResponse(response[i]);
          } else if (response[i].retry) {
            retry_instances.push(instances[i]);
          } else {
            console.log("ERROR1", response[i]);
            instances[i].setInfoBox('ERR1', 'https://github.com/bountysource/browser-extension/issues/22');
          }
        }

        if ((retry_instances.length > 0)) {
          if (attempts < 8) {
            //console.log("RETRYING", attempts, retry_instances.length, parseInt(Math.pow(1.5, attempts-1) * 1000));
            setTimeout(ThumbBox.loadAllData.bind(null, retry_instances, attempts), parseInt(Math.pow(1.5, attempts-1) * 1000));
          } else {
            console.log("ERROR2: too many retries");
            for (var j=0; j < retry_instances.length; j++) {
              retry_instances[j].setInfoBox('ERR2', 'https://github.com/bountysource/browser-extension/issues/10');
            }
          }
        }
      }
    }.bind(this));
  };

  ThumbBox.prototype.createDom = function() {
    this.container = document.createElement('div');
    this.container.classList.add(this.container_class);

    this.thumb_wrapper = document.createElement('div');
    this.thumb_wrapper.classList.add('thumb-wrapper');
    this.thumb_wrapper.addEventListener('click', this.thumbClicked.bind(this));
    this.container.appendChild(this.thumb_wrapper);

    this.thumb_image = document.createElement('img');
    this.thumb_image.src = this.image_path;
    this.thumb_wrapper.appendChild(this.thumb_image);

    this.info_wrapper = document.createElement('div');
    this.info_wrapper.classList.add('info-wrapper');
    this.container.appendChild(this.info_wrapper);
  };

  ThumbBox.prototype.setInfoBox = function(text, url) {
    while (this.info_wrapper.firstChild) {
      this.info_wrapper.removeChild(this.info_wrapper.firstChild);
    }
    if (text === 'spinner') {
      var spinner_image = document.createElement('img');
      spinner_image.src = BountysourceClient.imagePath('spinner.gif');
      this.info_wrapper.appendChild(spinner_image);
    } else if (url) {
      var link = document.createElement('a');
      link.href = url;
      link.target = '_blank';
      link.appendChild(document.createTextNode(text));
      this.info_wrapper.appendChild(link);
    } else {
      this.info_wrapper.appendChild(document.createTextNode(text));
    }
  };

  ThumbBox.prototype.setResponse = function(response) {
    this.api_response = response;
    this.setInfoBox(response.thumbs_up_count);
    if (response.has_thumbed_up) {
      this.container.classList.add('has-thumbed-up');
    } else {
      this.container.classList.remove('has-thumbed-up');
    }
  };

  ThumbBox.prototype.thumbClicked = function() {
    if (this.api_response && this.api_response.issue_id) {
      this.setInfoBox('spinner');
      BountysourceClient.api({
        method: 'POST',
        path: '/thumbs',
        body: {
          issue_id: this.api_response.issue_id,
          downvote: !!this.api_response.has_thumbed_up
        },
      }, function(response) {
        if (!response) {
          console.log("ERROR5", response);
          this.setInfoBox('ERR5', 'https://github.com/bountysource/browser-extension/issues/22');
        } else if (response.redirect_to) {
          document.location.href = response.redirect_to;
        } else if (response.issue_id) {
          this.setResponse(response);
        } else {
          console.log("ERROR6", response);
          this.setInfoBox('ERR6', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      }.bind(this));
    }
  };

  var matches;

  // Bountysource
  if (document.location.host.match(/\.bountysource\.com$/)) {
    document.body.classList.add('bountysource-thumbs-extension-is-installed');

  // Github (single page app so use fancy timers)
  } else if (document.location.href.match(/^https:\/\/github\.com\//)) {
    document.body.classList.add('bountysource-thumbs-github');

    var previousGithubUrl = null;
    var checkGithubUrlForChange = function() {
      if ((document.location.href !== previousGithubUrl) && !document.querySelector('.is-context-loading')) {
        previousGithubUrl = document.location.href;

        if ((matches = previousGithubUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/(?:issues|pull)\/\d+/)) && document.body.classList.contains('vis-public')) {
          var header = document.querySelector('#show_issue,.view-pull-request');

          if (!header.querySelector('.bountysource-thumbs-box')) {
            var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
            header.insertBefore(box.container, header.firstChild);
            ThumbBox.loadAllData([box]);
            BountysourceClient.google_analytics({ path: "thumbs/github/show" });
          }
        } else if (previousGithubUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/(?:issues|pulls|labels|milestones)/) && document.body.classList.contains('vis-public')) {
          var issues = document.querySelectorAll('.issue-title');
          var boxes = [];

          for (var i=0; i < issues.length; i++) {
            var issue_url = issues[i].getElementsByTagName('a')[0].href;
            var meta = issues[i].querySelector('.issue-meta');

            var old_box = issues[i].querySelector('.bountysource-thumbs-box-mini');
            if (old_box) {
              old_box.parentNode.removeChild(old_box);
            }
            var new_box = new ThumbBox({ issue_url: issue_url, size: 'small', impression: 'index' });
            boxes.push(new_box);
            meta.insertBefore(new_box.container, meta.firstChild);
          }
          ThumbBox.loadAllData(boxes);
          BountysourceClient.google_analytics({ path: "thumbs/github/index" });
        }

      }
      setTimeout(checkGithubUrlForChange, 100);
    };
    checkGithubUrlForChange();

  // Launchpad Issue Show
  } else if (matches = document.location.href.match(/^https:\/\/bugs\.launchpad\.net\/[^?]+\/\+bug\/\d+$/)) {
    document.body.classList.add('bountysource-thumbs-launchpad');
    var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
    var header = document.querySelector('.context-publication');
    header.parentNode.insertBefore(box.container, header);
    ThumbBox.loadAllData([box]);
    BountysourceClient.google_analytics({ path: "thumbs/launchpad/show" });

  // Launchpad Issue Index
  } else if (document.location.href.match(/^https:\/\/bugs\.launchpad\.net\//) && document.querySelector('#bugs-table-listing')) {
    document.body.classList.add('bountysource-thumbs-launchpad');

    // sorting and paginating uses browser-push and requires timers
    var previousLaunchpadUrl = null;
    var checkLaunchpadUrlForChange = function() {
      if ((document.location.href !== previousLaunchpadUrl) && document.querySelector('.yui3-overlay-indicator-hidden')) {
        previousLaunchpadUrl = document.location.href;

        var boxes = [];
        var rows = document.querySelectorAll('.buglisting-row');
        for (var i=0; i < rows.length; i++) {
          var target = rows[i].querySelector('.buglisting-col2 .buginfo-extra');

          // limit to first 500 issues
          if (i < 500) {
            var link = rows[i].querySelector('.bugtitle');
            var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
            boxes.push(box);
            target.insertBefore(box.container, target.firstChild);
          }
        }
        ThumbBox.loadAllData(boxes);
        BountysourceClient.google_analytics({ path: "thumbs/launchpad/index" });
      }
      setTimeout(checkLaunchpadUrlForChange, 100);
    };
    checkLaunchpadUrlForChange();

  // Bugzilla Issue Show
  } else if (matches = document.location.href.match(/^https?:\/\/[^?]*\/show_bug\.cgi\?id=\d+/)) {
    document.body.classList.add('bountysource-thumbs-bugzilla');

    var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
    var header = document.querySelector('.bz_alias_short_desc_container,.page-header');
    header.parentNode.insertBefore(box.container, header);
    if (['bugzilla.gnome.org','bugzilla.mozilla.org'].indexOf(document.location.host) >= 0) {
      document.body.classList.add('bountysource-thumbs-bugzilla-big-header');
    }
    ThumbBox.loadAllData([box]);
    BountysourceClient.google_analytics({ path: "thumbs/bugzilla/show" });

  // Bugzilla Issue List
  } else if (document.location.href.match(/^https?:\/\/[^?]*\/buglist\.cgi/)) {
    document.body.classList.add('bountysource-thumbs-bugzilla');

    var headers = document.querySelectorAll('tr.bz_buglist_header');
    for (var j=0; j < headers.length; j++) {
      var th = document.createElement('th');
      th.appendChild(document.createTextNode('+1'));
      headers[j].insertBefore(th, headers[j].firstChild);
    }

    var boxes = [];
    var trs = document.querySelectorAll('tr.bz_bugitem');
    for (var i=0; i < trs.length; i++) {
      var td = document.createElement('td');
      trs[i].insertBefore(td, trs[i].firstChild);

      // limit to first 100 issues
      if (i < 100) {
        var link = trs[i].getElementsByTagName('a')[0];
        var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
        boxes.push(box);
        td.appendChild(box.container);
      }
    }
    ThumbBox.loadAllData(boxes);
    BountysourceClient.google_analytics({ path: "thumbs/bugzilla/index" });

  // Trac
  } else if (document.querySelector('link[rel="help"][href$="TracGuide"]')) {
    document.body.classList.add('bountysource-thumbs-trac');

    if (matches = document.querySelector('#ticket.trac-content')) {
      // issue show
      var link = matches.querySelector('h2 a');
      var box = new ThumbBox({ issue_url: link.href, impression: 'show' });
      matches.insertBefore(box.container, matches.firstChild);
      ThumbBox.loadAllData([box]);
      BountysourceClient.google_analytics({ path: "thumbs/trac/show" });

    } else if ((matches = document.querySelectorAll('table.listing.tickets td.ticket, table.listing.tickets td.id')).length > 0) {
      // issues index
      var header = document.querySelector('table.listing.tickets tr.trac-columns');
      var th = document.createElement('th');
      th.appendChild(document.createTextNode('+1'));
      header.insertBefore(th, header.firstChild);

      var boxes = [];
      for (var i=0; i < matches.length; i++) {
        var td = document.createElement('td');
        matches[i].parentNode.insertBefore(td, matches[i]);

        var link = matches[i].getElementsByTagName('a')[0];
        var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
        boxes.push(box);
        td.appendChild(box.container);
      }
      ThumbBox.loadAllData(boxes);
      BountysourceClient.google_analytics({ path: "thumbs/trac/index" });
    }

  // // Jira
  // //   TODO: ERR2 (backend doesn't seem to update thubs_up_count or votes_count)
  // //   TODO: inject thumbs into split-view
  // } else if (document.querySelector('meta[name="application-name"][content="JIRA"]')) {
  //   document.body.classList.add('bountysource-thumbs-jira');
  //
  //   var previousJiraUrl = null;
  //   var checkJiraUrlForChange = function() {
  //     if ((document.location.href !== previousJiraUrl) && (matches = document.querySelector('#issue-content')) && !document.querySelector('#issue-content .bountysource-thumbs-box')) {
  //       // issue show (also works with split view)
  //       previousJiraUrl = document.location.href;
  //
  //       var link = matches.querySelector('#key-val');
  //       var box = new ThumbBox({ issue_url: link.href, impression: 'show' });
  //       matches.insertBefore(box.container, matches.firstChild);
  //       ThumbBox.loadAllData([box]);
  //       BountysourceClient.google_analytics({ path: "thumbs/jira/show" });
  //     } else if ((document.location.href !== previousJiraUrl) && (matches = document.querySelectorAll('#issuetable.navigator-results .issuerow .issuetype')).length > 0) {
  //       // issue index (non-split view)
  //       previousJiraUrl = document.location.href;
  //
  //       var header = document.querySelector('#issuetable .rowHeader');
  //       var th = document.createElement('th');
  //       th.appendChild(document.createTextNode('+1'));
  //       header.insertBefore(th, header.firstChild);
  //
  //       var boxes = [];
  //       for (var i=0; i < matches.length; i++) {
  //         var td = document.createElement('td');
  //         matches[i].parentNode.insertBefore(td, matches[i]);
  //
  //         var link = matches[i].getElementsByTagName('a')[0];
  //         var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
  //         boxes.push(box);
  //         td.appendChild(box.container);
  //       }
  //       ThumbBox.loadAllData(boxes);
  //       BountysourceClient.google_analytics({ path: "thumbs/jira/index" });
  //
  //     }
  //     setTimeout(checkJiraUrlForChange, 100);
  //   };
  //   checkJiraUrlForChange();
  }
})();
<commit_msg>setTimeout uses explicit function<commit_after>(function() {

  var ThumbBox = function(options) {
    options = options || {};
    this.issue_url = options.issue_url;
    this.impression = options.impression;
    if (options.size === 'small') {
      this.container_class = 'bountysource-thumbs-box-mini';
      this.image_path = BountysourceClient.imagePath('thumbsup-20.png');
    } else {
      this.container_class = 'bountysource-thumbs-box';
      this.image_path = BountysourceClient.imagePath('thumbsup-32.png');
    }

    this.createDom();
    this.setInfoBox('spinner');
  };

  ThumbBox.loadAllData = function(instances, attempts) {
    var urls = [];
    for (var i=0; i < instances.length; i++) {
      urls.push(instances[i].issue_url);
    }

    if (urls.length === 0) {
      return;
    }

    attempts = (attempts || 0) + 1;
    BountysourceClient.api({
      method: 'POST',
      path: '/thumbs/index',
      body: {
        urls: urls,
        impression: instances[0].impression
      }
    }, function(response) {
      if (!response) {
        console.log("ERROR3", response);
        for (var i=0; i < instances.length; i++) {
          instances[i].setInfoBox('ERR3', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      } else if (response.length !== instances.length) {
        console.log("ERROR4, unexpected response", instances.length, response.length);
        for (var i=0; i < instances.length; i++) {
          instances[i].setInfoBox('ERR4', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      } else {
        var retry_instances = [];

        for (var i=0; i < response.length; i++) {
          if (response[i].issue_id) {
            instances[i].setResponse(response[i]);
          } else if (response[i].retry) {
            retry_instances.push(instances[i]);
          } else {
            console.log("ERROR1", response[i]);
            instances[i].setInfoBox('ERR1', 'https://github.com/bountysource/browser-extension/issues/22');
          }
        }

        if ((retry_instances.length > 0)) {
          if (attempts < 8) {
            //console.log("RETRYING", attempts, retry_instances.length, parseInt(Math.pow(1.5, attempts-1) * 1000));
            setTimeout(function() { ThumbBox.loadAllData(retry_instances, attempts); }, parseInt(Math.pow(1.5, attempts-1) * 1000));
          } else {
            console.log("ERROR2: too many retries");
            for (var j=0; j < retry_instances.length; j++) {
              retry_instances[j].setInfoBox('ERR2', 'https://github.com/bountysource/browser-extension/issues/10');
            }
          }
        }
      }
    }.bind(this));
  };

  ThumbBox.prototype.createDom = function() {
    this.container = document.createElement('div');
    this.container.classList.add(this.container_class);

    this.thumb_wrapper = document.createElement('div');
    this.thumb_wrapper.classList.add('thumb-wrapper');
    this.thumb_wrapper.addEventListener('click', this.thumbClicked.bind(this));
    this.container.appendChild(this.thumb_wrapper);

    this.thumb_image = document.createElement('img');
    this.thumb_image.src = this.image_path;
    this.thumb_wrapper.appendChild(this.thumb_image);

    this.info_wrapper = document.createElement('div');
    this.info_wrapper.classList.add('info-wrapper');
    this.container.appendChild(this.info_wrapper);
  };

  ThumbBox.prototype.setInfoBox = function(text, url) {
    while (this.info_wrapper.firstChild) {
      this.info_wrapper.removeChild(this.info_wrapper.firstChild);
    }
    if (text === 'spinner') {
      var spinner_image = document.createElement('img');
      spinner_image.src = BountysourceClient.imagePath('spinner.gif');
      this.info_wrapper.appendChild(spinner_image);
    } else if (url) {
      var link = document.createElement('a');
      link.href = url;
      link.target = '_blank';
      link.appendChild(document.createTextNode(text));
      this.info_wrapper.appendChild(link);
    } else {
      this.info_wrapper.appendChild(document.createTextNode(text));
    }
  };

  ThumbBox.prototype.setResponse = function(response) {
    this.api_response = response;
    this.setInfoBox(response.thumbs_up_count);
    if (response.has_thumbed_up) {
      this.container.classList.add('has-thumbed-up');
    } else {
      this.container.classList.remove('has-thumbed-up');
    }
  };

  ThumbBox.prototype.thumbClicked = function() {
    if (this.api_response && this.api_response.issue_id) {
      this.setInfoBox('spinner');
      BountysourceClient.api({
        method: 'POST',
        path: '/thumbs',
        body: {
          issue_id: this.api_response.issue_id,
          downvote: !!this.api_response.has_thumbed_up
        },
      }, function(response) {
        if (!response) {
          console.log("ERROR5", response);
          this.setInfoBox('ERR5', 'https://github.com/bountysource/browser-extension/issues/22');
        } else if (response.redirect_to) {
          document.location.href = response.redirect_to;
        } else if (response.issue_id) {
          this.setResponse(response);
        } else {
          console.log("ERROR6", response);
          this.setInfoBox('ERR6', 'https://github.com/bountysource/browser-extension/issues/22');
        }
      }.bind(this));
    }
  };

  var matches;

  // Bountysource
  if (document.location.host.match(/\.bountysource\.com$/)) {
    document.body.classList.add('bountysource-thumbs-extension-is-installed');

  // Github (single page app so use fancy timers)
  } else if (document.location.href.match(/^https:\/\/github\.com\//)) {
    document.body.classList.add('bountysource-thumbs-github');

    var previousGithubUrl = null;
    var checkGithubUrlForChange = function() {
      if ((document.location.href !== previousGithubUrl) && !document.querySelector('.is-context-loading')) {
        previousGithubUrl = document.location.href;

        if ((matches = previousGithubUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/(?:issues|pull)\/\d+/)) && document.body.classList.contains('vis-public')) {
          var header = document.querySelector('#show_issue,.view-pull-request');

          if (!header.querySelector('.bountysource-thumbs-box')) {
            var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
            header.insertBefore(box.container, header.firstChild);
            ThumbBox.loadAllData([box]);
            BountysourceClient.google_analytics({ path: "thumbs/github/show" });
          }
        } else if (previousGithubUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/(?:issues|pulls|labels|milestones)/) && document.body.classList.contains('vis-public')) {
          var issues = document.querySelectorAll('.issue-title');
          var boxes = [];

          for (var i=0; i < issues.length; i++) {
            var issue_url = issues[i].getElementsByTagName('a')[0].href;
            var meta = issues[i].querySelector('.issue-meta');

            var old_box = issues[i].querySelector('.bountysource-thumbs-box-mini');
            if (old_box) {
              old_box.parentNode.removeChild(old_box);
            }
            var new_box = new ThumbBox({ issue_url: issue_url, size: 'small', impression: 'index' });
            boxes.push(new_box);
            meta.insertBefore(new_box.container, meta.firstChild);
          }
          ThumbBox.loadAllData(boxes);
          BountysourceClient.google_analytics({ path: "thumbs/github/index" });
        }

      }
      setTimeout(function() { checkGithubUrlForChange(); }, 100);
    };
    checkGithubUrlForChange();

  // Launchpad Issue Show
  } else if (matches = document.location.href.match(/^https:\/\/bugs\.launchpad\.net\/[^?]+\/\+bug\/\d+$/)) {
    document.body.classList.add('bountysource-thumbs-launchpad');
    var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
    var header = document.querySelector('.context-publication');
    header.parentNode.insertBefore(box.container, header);
    ThumbBox.loadAllData([box]);
    BountysourceClient.google_analytics({ path: "thumbs/launchpad/show" });

  // Launchpad Issue Index
  } else if (document.location.href.match(/^https:\/\/bugs\.launchpad\.net\//) && document.querySelector('#bugs-table-listing')) {
    document.body.classList.add('bountysource-thumbs-launchpad');

    // sorting and paginating uses browser-push and requires timers
    var previousLaunchpadUrl = null;
    var checkLaunchpadUrlForChange = function() {
      if ((document.location.href !== previousLaunchpadUrl) && document.querySelector('.yui3-overlay-indicator-hidden')) {
        previousLaunchpadUrl = document.location.href;

        var boxes = [];
        var rows = document.querySelectorAll('.buglisting-row');
        for (var i=0; i < rows.length; i++) {
          var target = rows[i].querySelector('.buglisting-col2 .buginfo-extra');

          // limit to first 500 issues
          if (i < 500) {
            var link = rows[i].querySelector('.bugtitle');
            var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
            boxes.push(box);
            target.insertBefore(box.container, target.firstChild);
          }
        }
        ThumbBox.loadAllData(boxes);
        BountysourceClient.google_analytics({ path: "thumbs/launchpad/index" });
      }
      setTimeout(function() { checkLaunchpadUrlForChange(); }, 100);
    };
    checkLaunchpadUrlForChange();

  // Bugzilla Issue Show
  } else if (matches = document.location.href.match(/^https?:\/\/[^?]*\/show_bug\.cgi\?id=\d+/)) {
    document.body.classList.add('bountysource-thumbs-bugzilla');

    var box = new ThumbBox({ issue_url: matches[0], impression: 'show' });
    var header = document.querySelector('.bz_alias_short_desc_container,.page-header');
    header.parentNode.insertBefore(box.container, header);
    if (['bugzilla.gnome.org','bugzilla.mozilla.org'].indexOf(document.location.host) >= 0) {
      document.body.classList.add('bountysource-thumbs-bugzilla-big-header');
    }
    ThumbBox.loadAllData([box]);
    BountysourceClient.google_analytics({ path: "thumbs/bugzilla/show" });

  // Bugzilla Issue List
  } else if (document.location.href.match(/^https?:\/\/[^?]*\/buglist\.cgi/)) {
    document.body.classList.add('bountysource-thumbs-bugzilla');

    var headers = document.querySelectorAll('tr.bz_buglist_header');
    for (var j=0; j < headers.length; j++) {
      var th = document.createElement('th');
      th.appendChild(document.createTextNode('+1'));
      headers[j].insertBefore(th, headers[j].firstChild);
    }

    var boxes = [];
    var trs = document.querySelectorAll('tr.bz_bugitem');
    for (var i=0; i < trs.length; i++) {
      var td = document.createElement('td');
      trs[i].insertBefore(td, trs[i].firstChild);

      // limit to first 100 issues
      if (i < 100) {
        var link = trs[i].getElementsByTagName('a')[0];
        var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
        boxes.push(box);
        td.appendChild(box.container);
      }
    }
    ThumbBox.loadAllData(boxes);
    BountysourceClient.google_analytics({ path: "thumbs/bugzilla/index" });

  // Trac
  } else if (document.querySelector('link[rel="help"][href$="TracGuide"]')) {
    document.body.classList.add('bountysource-thumbs-trac');

    if (matches = document.querySelector('#ticket.trac-content')) {
      // issue show
      var link = matches.querySelector('h2 a');
      var box = new ThumbBox({ issue_url: link.href, impression: 'show' });
      matches.insertBefore(box.container, matches.firstChild);
      ThumbBox.loadAllData([box]);
      BountysourceClient.google_analytics({ path: "thumbs/trac/show" });

    } else if ((matches = document.querySelectorAll('table.listing.tickets td.ticket, table.listing.tickets td.id')).length > 0) {
      // issues index
      var header = document.querySelector('table.listing.tickets tr.trac-columns');
      var th = document.createElement('th');
      th.appendChild(document.createTextNode('+1'));
      header.insertBefore(th, header.firstChild);

      var boxes = [];
      for (var i=0; i < matches.length; i++) {
        var td = document.createElement('td');
        matches[i].parentNode.insertBefore(td, matches[i]);

        var link = matches[i].getElementsByTagName('a')[0];
        var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
        boxes.push(box);
        td.appendChild(box.container);
      }
      ThumbBox.loadAllData(boxes);
      BountysourceClient.google_analytics({ path: "thumbs/trac/index" });
    }

  // // Jira
  // //   TODO: ERR2 (backend doesn't seem to update thubs_up_count or votes_count)
  // //   TODO: inject thumbs into split-view
  // } else if (document.querySelector('meta[name="application-name"][content="JIRA"]')) {
  //   document.body.classList.add('bountysource-thumbs-jira');
  //
  //   var previousJiraUrl = null;
  //   var checkJiraUrlForChange = function() {
  //     if ((document.location.href !== previousJiraUrl) && (matches = document.querySelector('#issue-content')) && !document.querySelector('#issue-content .bountysource-thumbs-box')) {
  //       // issue show (also works with split view)
  //       previousJiraUrl = document.location.href;
  //
  //       var link = matches.querySelector('#key-val');
  //       var box = new ThumbBox({ issue_url: link.href, impression: 'show' });
  //       matches.insertBefore(box.container, matches.firstChild);
  //       ThumbBox.loadAllData([box]);
  //       BountysourceClient.google_analytics({ path: "thumbs/jira/show" });
  //     } else if ((document.location.href !== previousJiraUrl) && (matches = document.querySelectorAll('#issuetable.navigator-results .issuerow .issuetype')).length > 0) {
  //       // issue index (non-split view)
  //       previousJiraUrl = document.location.href;
  //
  //       var header = document.querySelector('#issuetable .rowHeader');
  //       var th = document.createElement('th');
  //       th.appendChild(document.createTextNode('+1'));
  //       header.insertBefore(th, header.firstChild);
  //
  //       var boxes = [];
  //       for (var i=0; i < matches.length; i++) {
  //         var td = document.createElement('td');
  //         matches[i].parentNode.insertBefore(td, matches[i]);
  //
  //         var link = matches[i].getElementsByTagName('a')[0];
  //         var box = new ThumbBox({ issue_url: link.href, size: 'small', impression: 'index' });
  //         boxes.push(box);
  //         td.appendChild(box.container);
  //       }
  //       ThumbBox.loadAllData(boxes);
  //       BountysourceClient.google_analytics({ path: "thumbs/jira/index" });
  //
  //     }
  //     setTimeout(function() { checkJiraUrlForChange(); }, 100);
  //   };
  //   checkJiraUrlForChange();
  }
})();
<|endoftext|>


<commit_before>const execSync = require('child_process').execSync
const { SiteChecker } = require('broken-link-checker')

const GIT_DIFF_NAME_STATUS_LAST_COMMIT = 'git diff --name-status HEAD~1'
const MARKDOWN_EXTENSION = '.md'
const ORIGIN_URL = 'http://localhost:3000/'

const isMarkdownFile = (line) => line.endsWith(MARKDOWN_EXTENSION)

const GIT_STATUSES = {
  ADDED: 'A',
  COPY_EDIT: 'C',
  MODIFIED: 'M',
  RENAME_EDIT: 'R',
  DELETED: 'D',
  UNMERGED: 'U',
}
const isCopiedFile = (line) => line.startsWith(GIT_STATUSES.COPY_EDIT)
const isModifiedFile = (line) => line.startsWith(GIT_STATUSES.MODIFIED)
const isAddedFile = (line) => line.startsWith(GIT_STATUSES.ADDED)
const GIT_STATUS_PREDICATES = [isCopiedFile, isModifiedFile, isAddedFile]

const isCheckableGitStatus = (line) => {
  return GIT_STATUS_PREDICATES.some((fn) => fn(line))
}

const removeGitStatusFromLine = (line) => {
  // Each line will have a prefix of one of the following letters:
  // M (modified),
  // C (changed),
  // R (removed),
  // A (added),
  // D (deleted),
  // U (unmerged; file has conflicts after merge)
  // The letter will be followed by a tab: '\t'
  const STATUS_LENGTH = 2
  return line.slice(STATUS_LENGTH)
}

const removeMarkdownExtension = (line) => {
  const LENGTH_TO_TRIM = MARKDOWN_EXTENSION.length
  return line.slice(0, line.length - LENGTH_TO_TRIM)
}

const removeContentDirectoryPrefix = (line) => {
  const LENGTH_TO_TRIM_FROM_START = 'content/'.length
  return line.slice(LENGTH_TO_TRIM_FROM_START)
}

const reduceChangelogChangesToOne = (
  all = { hasChangelogEdits: false, paths: [] },
  file,
  index,
  arr
) => {
  const CHANGELOG_DIRECTORY = '_changelogs/'
  if (file.startsWith(CHANGELOG_DIRECTORY)) {
    if (!all.hasChangelogEdits) {
      const CHANGELOG_ROUTE = 'guides/references/changelog'
      return {
        hasChangelogEdits: true,
        paths: all.paths.concat(CHANGELOG_ROUTE),
      }
    }
    return all
  }
  if (index < arr.length - 1) {
    return {
      ...all,
      paths: all.paths.concat(file),
    }
  }
  return all.paths
}

const convertRelativeUrlToAbsolute = (path) => `${ORIGIN_URL}${path}`

const getGitDiffList = () => {
  return `M\tcontent/_changelogs/6.6.0.md
M\tcontent/_changelogs/6.7.0.md
M\tcontent/_changelogs/6.7.1.md
M\tcontent/_changelogs/6.8.0.md
D\tcontent/_partial/allowed_test_config.md
D\tcontent/_partial/chromium_download.md
D\tcontent/_partial/code_runs_in_node.md
D\tcontent/_partial/cypress_env_var_warning.md
D\tcontent/_partial/errors_anatomy.md
D\tcontent/_partial/linux_dependencies.md
D\tcontent/_partial/network_stubbing_warning.md
D\tcontent/_partial/then_should_difference.md
D\tcontent/_partial/vpn_allowed_list.md
D\tcontent/_partial/xhr_stubbing_deprecated.md
M\tcontent/api/plugins/browser-launch-api.md
M\tcontent/api/plugins/configuration-api.md
M\tcontent/guides/continuous-integration/aws-codebuild.md
M\tcontent/guides/continuous-integration/bitbucket-pipelines.md
M\tcontent/guides/dashboard/jira-integration.md
M\tcontent/guides/dashboard/smart-orchestration.md
M\tcontent/guides/testing-strategies/google-authentication.md
M\tcontent/guides/tooling/plugins-guide.md`.split('\n')

  // return execSync(GIT_DIFF_NAME_STATUS_LAST_COMMIT)
  // .toString()
  // .split('\n')
}

const makeTestUrl = (url) => async () => {
  return new Promise((resolve, reject) => {
    const siteChecker = new SiteChecker({
      excludeExternalLinks: true,
      honorRobotExclusions: false,
    })
      .on('error', (error) => reject(error))
      .on('end', () => {
        resolve()
      })

    siteChecker.enqueue(url)
  })
}

const main = () => {
  const urls = getGitDiffList()
    .filter(isMarkdownFile)
    .filter(isCheckableGitStatus)
    .map(removeGitStatusFromLine)
    .map(removeMarkdownExtension)
    .map(removeContentDirectoryPrefix)
    .reduce(reduceChangelogChangesToOne, {
      hasChangelogEdits: undefined,
      paths: [],
    })
    .map(convertRelativeUrlToAbsolute)

  console.log(urls)
}

main()
<commit_msg>feat: broken link checker script to diff changed markdown files<commit_after>/* eslint-disable no-console */
const execSync = require('child_process').execSync
const { SiteChecker } = require('broken-link-checker')
const chalk = require('chalk')

const logger = {
  log: (...args) => {
    console.log(`${chalk.yellow(`[${new Date().toISOString()}]:`)} `, ...args)
  },
  error: (...args) => {
    console.error(
      `${chalk.bgRed(`[${new Date().toISOString()}]: ⛔ ERROR:`)} `,
      ...args
    )
  },
}

const GIT_DIFF_NAME_STATUS_LAST_COMMIT = 'git diff --name-status HEAD~1'
const MARKDOWN_EXTENSION = '.md'
const ORIGIN_URL = 'http://localhost:3000/'

const isMarkdownFile = (line) => line.endsWith(MARKDOWN_EXTENSION)

const GIT_STATUSES = {
  ADDED: 'A',
  COPY_EDIT: 'C',
  MODIFIED: 'M',
  RENAME_EDIT: 'R',
  DELETED: 'D',
  UNMERGED: 'U',
}
const isCopiedFile = (line) => line.startsWith(GIT_STATUSES.COPY_EDIT)
const isModifiedFile = (line) => line.startsWith(GIT_STATUSES.MODIFIED)
const isAddedFile = (line) => line.startsWith(GIT_STATUSES.ADDED)
const GIT_STATUS_PREDICATES = [isCopiedFile, isModifiedFile, isAddedFile]

const isCheckableGitStatus = (line) => {
  return GIT_STATUS_PREDICATES.some((fn) => fn(line))
}

const removeGitStatusFromLine = (line) => {
  // Each line will have a prefix of one of the git status letters
  // followed by a tab character: '\t'
  const STATUS_LENGTH = 2

  return line.slice(STATUS_LENGTH)
}

const removeMarkdownExtension = (line) => {
  const LENGTH_TO_TRIM = MARKDOWN_EXTENSION.length

  return line.slice(0, line.length - LENGTH_TO_TRIM)
}

const removeContentDirectoryPrefix = (line) => {
  const LENGTH_TO_TRIM_FROM_START = 'content/'.length

  return line.slice(LENGTH_TO_TRIM_FROM_START)
}

const reduceChangelogChangesToOne = (
  all = { hasChangelogEdits: false, paths: [] },
  file,
  index,
  arr
) => {
  const CHANGELOG_DIRECTORY = '_changelogs/'

  if (file.startsWith(CHANGELOG_DIRECTORY)) {
    if (!all.hasChangelogEdits) {
      const CHANGELOG_ROUTE = 'guides/references/changelog'

      return {
        hasChangelogEdits: true,
        paths: all.paths.concat(CHANGELOG_ROUTE),
      }
    }

    return all
  }

  if (index < arr.length - 1) {
    return {
      ...all,
      paths: all.paths.concat(file),
    }
  }

  return all.paths
}

const convertRelativeUrlToAbsolute = (path) => `${ORIGIN_URL}${path}`

const getGitDiffList = () => {
  // If you need test data,
  // uncommend the following lines:
  //
  // return `M\tcontent/_changelogs/6.6.0.md
  // M\tcontent/_changelogs/6.7.0.md
  // M\tcontent/_changelogs/6.7.1.md
  // M\tcontent/_changelogs/6.8.0.md
  // D\tcontent/_partial/allowed_test_config.md
  // D\tcontent/_partial/chromium_download.md
  // D\tcontent/_partial/code_runs_in_node.md
  // D\tcontent/_partial/cypress_env_var_warning.md
  // D\tcontent/_partial/errors_anatomy.md
  // D\tcontent/_partial/linux_dependencies.md
  // D\tcontent/_partial/network_stubbing_warning.md
  // D\tcontent/_partial/then_should_difference.md
  // D\tcontent/_partial/vpn_allowed_list.md
  // D\tcontent/_partial/xhr_stubbing_deprecated.md
  // M\tcontent/api/plugins/browser-launch-api.md
  // M\tcontent/api/plugins/configuration-api.md
  // M\tcontent/guides/continuous-integration/aws-codebuild.md
  // M\tcontent/guides/continuous-integration/bitbucket-pipelines.md
  // M\tcontent/guides/dashboard/jira-integration.md
  // M\tcontent/guides/dashboard/smart-orchestration.md
  // M\tcontent/guides/testing-strategies/google-authentication.md
  // M\tcontent/guides/tooling/plugins-guide.md`.split('\n')

  const diff = execSync(GIT_DIFF_NAME_STATUS_LAST_COMMIT).toString().split('\n')

  if (Array.isArray(diff)) {
    return diff
  }

  return [diff]
}

const prettyPrintStatusCode = (statusCode) => {
  if (statusCode >= 400) {
    return chalk.bgRed(`ERROR ⛔`)
  }

  return chalk.green(`OK ✅`)
}

const makeSiteCheckerForUrl = (url) => {return async () => {
  return new Promise((resolve, reject) => {
    /**
     * The promise resolves the following:
     * @type Array<{ originUrl: string, brokenUrl: string }>
     */
    let brokenLinkRecords = []
    const siteChecker = new SiteChecker(
      {
        excludeExternalLinks: true,
        honorRobotExclusions: false,
      },
      {
        error: (error) => {
          logger.error('An error occurred', error)
        },
        html: (tree, robots, response, pageUrl) => {
          const htmlNode = tree.childNodes.find(
            (node) => node.tagName === 'html'
          )
          const headNode = htmlNode.childNodes.find(
            (node) => node.tagName === 'head'
          )
          const titleNode = headNode.childNodes.find(
            (node) => node.tagName === 'title'
          )
          const titleTextNode = titleNode.childNodes.find(
            (node) => node.nodeName === '#text'
          )
          const is404 = titleTextNode.value.includes(
            '404 | Cypress Documentation'
          )

          if (is404) {
            logger.error(
              `Broken link found on page ${url}: ${chalk.bgRed(pageUrl)}`
            )

            brokenLinkRecords.push({
              originUrl: url,
              brokenUrl: pageUrl,
            })
          }
        },
        link: (link) => {
          logger.log(
            `${prettyPrintStatusCode(link.http.statusCode)} ${
              link.url.resolved
            }`
          )
        },
        end: () => {
          logger.log(`Finished for url ${url}`)
          resolve(brokenLinkRecords)
        },
        complete: () => {
          logger.log('complete handler called')
        },
      }
    )

    logger.log(`🔗 Starting link checker for url: ${url}`)
    siteChecker.enqueue(url)
  })
}}

const main = async () => {
  console.time('changedFilesBrokenLinkChecker')
  const urls = getGitDiffList()
    .filter(isMarkdownFile)
    .filter(isCheckableGitStatus)
    .map(removeGitStatusFromLine)
    .map(removeMarkdownExtension)
    .map(removeContentDirectoryPrefix)
    .reduce(reduceChangelogChangesToOne, {
      hasChangelogEdits: undefined,
      paths: [],
    })
    .map(convertRelativeUrlToAbsolute)

  logger.log('URLs to check: ', urls)

  const siteCheckers = urls.map(makeSiteCheckerForUrl)

  let brokenLinkRecords = []

  for (const siteChecker of siteCheckers) {
    const records = await siteChecker()

    brokenLinkRecords = [...brokenLinkRecords, ...records]
  }
  logger.log(
    `Number of broken URLs found: ${
      brokenLinkRecords.length
        ? `${chalk.bgRed(brokenLinkRecords.length)}`
        : `${chalk.bgGreen(brokenLinkRecords.length)} ✅`
    }`
  )

  brokenLinkRecords.forEach(({ originUrl, brokenUrl }) => {
    logger.error(`************************`)
    logger.error(`Broken URL on page: ${originUrl}`)
    logger.error(`Broken URL: ${brokenUrl}`)
  })

  console.timeEnd('changedFilesBrokenLinkChecker')
  if (brokenLinkRecords.length) {
    process.exit(1)
  }
}

main()
<|endoftext|>


<commit_before>// For inspiration on your webpack configuration, see:
// https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/client
// https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/client

const webpack = require('webpack');
const { resolve } = require('path');

const ManifestPlugin = require('webpack-manifest-plugin');
const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');

const configPath = resolve('..', 'config');
const { devBuild, manifest, webpackOutputPath, webpackPublicOutputDir } =
  webpackConfigLoader(configPath);

const config = {

  context: resolve(__dirname),

  entry: {
    'webpack-bundle': [
      'es5-shim/es5-shim',
      'es5-shim/es5-sham',
      'babel-polyfill',
      './app/bundles/HelloWorld/startup/registration',
    ],
  },

  output: {
    // Name comes from the entry section.
    filename: '[name]-[hash].js',

    // Leading slash is necessary
    publicPath: `/${webpackPublicOutputDir}`,
    path: webpackOutputPath,
  },

  resolve: {
    extensions: ['.js', '.jsx'],
  },

  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
      DEBUG: false,
    }),
    new ManifestPlugin({ fileName: manifest, writeToFileEmit: true }),
  ],

  module: {
    rules: [
      {
        test: require.resolve('react'),
        use: {
          loader: 'imports-loader',
          options: {
            shim: 'es5-shim/es5-shim',
            sham: 'es5-shim/es5-sham',
          },
        },
      },
      {
        test: /\.jsx?$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ],
  },
};

module.exports = config;

if (devBuild) {
  console.log('Webpack dev build for Rails'); // eslint-disable-line no-console
  module.exports.devtool = 'eval-source-map';
} else {
  console.log('Webpack production build for Rails'); // eslint-disable-line no-console
}
<commit_msg>update correct entry<commit_after>// For inspiration on your webpack configuration, see:
// https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/client
// https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/client

const webpack = require('webpack');
const { resolve } = require('path');

const ManifestPlugin = require('webpack-manifest-plugin');
const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');

const configPath = resolve('..', 'config');
const { devBuild, manifest, webpackOutputPath, webpackPublicOutputDir } =
  webpackConfigLoader(configPath);

const config = {

  context: resolve(__dirname),

  entry: {
    'webpack-bundle': [
      'es5-shim/es5-shim',
      'es5-shim/es5-sham',
      'babel-polyfill',
      './app/bundles/RentersFriend/startup/registration',
    ],
  },

  output: {
    // Name comes from the entry section.
    filename: '[name]-[hash].js',

    // Leading slash is necessary
    publicPath: `/${webpackPublicOutputDir}`,
    path: webpackOutputPath,
  },

  resolve: {
    extensions: ['.js', '.jsx'],
  },

  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
      DEBUG: false,
    }),
    new ManifestPlugin({ fileName: manifest, writeToFileEmit: true }),
  ],

  module: {
    rules: [
      {
        test: require.resolve('react'),
        use: {
          loader: 'imports-loader',
          options: {
            shim: 'es5-shim/es5-shim',
            sham: 'es5-shim/es5-sham',
          },
        },
      },
      {
        test: /\.jsx?$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ],
  },
};

module.exports = config;

if (devBuild) {
  console.log('Webpack dev build for Rails'); // eslint-disable-line no-console
  module.exports.devtool = 'eval-source-map';
} else {
  console.log('Webpack production build for Rails'); // eslint-disable-line no-console
}
<|endoftext|>


<commit_before><commit_msg>remove useless files<commit_after><|endoftext|>


<commit_before>// 3rd party plugins

export default function init(config) {
    if (config.google_analytics_id) {
        (function(i, s, o, g, r, a, m) {
            i['GoogleAnalyticsObject'] = r;
            (i[r] =
                i[r] ||
                function() {
                    (i[r].q = i[r].q || []).push(arguments);
                }),
                (i[r].l = 1 * new Date());
            (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
            a.async = 1;
            a.src = g;
            m.parentNode.insertBefore(a, m);
        })(
            window,
            document,
            'script',
            'https://www.google-analytics.com/analytics.js',
            'ga'
        );
        ga('create', config.google_analytics_id, 'auto');
    }

    if (config.facebook_app_id) {
        window.fbAsyncInit = function() {
            FB.init({
                appId: config.facebook_app_id,
                xfbml: true,
                version: 'v2.6',
            });
        };
        (function(d, s, id) {
            var js,
                fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) {
                return;
            }
            js = d.createElement(s);
            js.id = id;
            js.src = '//connect.facebook.net/en_US/sdk.js';
            fjs.parentNode.insertBefore(js, fjs);
        })(document, 'script', 'facebook-jssdk');
    }
}
<commit_msg>set google-analytics sample rate to 5, refs #2379<commit_after>// 3rd party plugins

export default function init(config) {
    if (config.google_analytics_id) {
        (function(i, s, o, g, r, a, m) {
            i['GoogleAnalyticsObject'] = r;
            (i[r] =
                i[r] ||
                function() {
                    (i[r].q = i[r].q || []).push(arguments);
                }),
                (i[r].l = 1 * new Date());
            (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
            a.async = 1;
            a.src = g;
            m.parentNode.insertBefore(a, m);
        })(
            window,
            document,
            'script',
            'https://www.google-analytics.com/analytics.js',
            'ga'
        );
        ga('create', {
            trackingId:  config.google_analytics_id,
            cookieDomain: 'auto',
            sampleRate: 5
        });
    }

    if (config.facebook_app_id) {
        window.fbAsyncInit = function() {
            FB.init({
                appId: config.facebook_app_id,
                xfbml: true,
                version: 'v2.6',
            });
        };
        (function(d, s, id) {
            var js,
                fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) {
                return;
            }
            js = d.createElement(s);
            js.id = id;
            js.src = '//connect.facebook.net/en_US/sdk.js';
            fjs.parentNode.insertBefore(js, fjs);
        })(document, 'script', 'facebook-jssdk');
    }
}
<|endoftext|>


<commit_before>/* **************************************************************

   Copyright 2013 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

************************************************************** */

//    !!! ->   TODO: replace 'username' in the line below with the merchants username.     <- !!!

var seo_robots = function(_app) {
	var r = {

	vars : {
		pages : [],
		pagesLoaded : false
		},

////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

	callbacks : {
//executed when extension is loaded. should include any validation that needs to occur.
		init : {
			onSuccess : function()	{
				var r = false; //return false if extension won't load for some reason (account config, dependencies, etc).

				if(_app.vars._robotGreeting){
					_app.ext.seo_robots.u.welcomeRobot(_app.vars._robotGreeting);
					}
				else {
					_robots.hello = _app.ext.seo_robots.u.welcomeRobot;
					}
				//Replace the _robots.next default functionality with some real stuff

				_robots.next = function(){
					if(_app.ext.seo_robots.vars.pagesLoaded){
						var p = _app.ext.seo_robots.vars.pages.splice(0,1)[0];
						if(typeof p == 'undefined'){
							_robots.status = function(){ return -1; }
							return false;
							}
						var status = 100;
						var hasRun = false;
						_robots.status = function(){
							if(hasRun){
								if(_app.ext.quickstart.vars.showContentFinished && _app.ext.quickstart.vars.showContentCompleteFired){
									status = 200;
									}
								else {
									status = 100;
									}
								}
							else {
								if(typeof p == 'string' && p.indexOf('#!') == 0){
									_app.ext.quickstart.vars.showContentFinished = false;
									_app.ext.quickstart.vars.showContentCompleteFired = false;
									window.location.hash = p;
									}
								else if(typeof p == 'object'){ //p is an object
									var infoObj = {};
									switch(p.type){
										case "pid":
											infoObj = {
												pageType : "product",
												pid : p.id
												};
											break;
										case "navcat" :
											infoObj = {
												pageType : "category",
												navcat : p.id
												};
											break;
										case "list" :
											dump("LIST "+p.id+" SKIPPED IN PAGE BUILDING");
											break;
										default :
											dump("Unrecognized pageInfo type: "+p.type+" full obj follows:");
											dump(p);
											break;
										}
									$('#globalMessaging').intervaledEmpty();
									showContent('',infoObj);
									}
								else {
									status = 404;
									}
								hasRun = true;
								}
							return status;
							}
						}
					else {
						_robots.status = function(){
							if(_app.ext.seo_robots.vars.pagesLoaded){
								return 204;
								}
							else {
								return 100;
								}
							}
						}
					}

				//if there is any functionality required for this extension to load, put it here. such as a check for async google, the FB object, etc. return false if dependencies are not present. don't check for other extensions.
				r = true;

				return r;
				},
			onError : function()	{
//errors will get reported for this callback as part of the extensions loading.  This is here for extra error handling purposes.
//you may or may not need it.
				_app.u.dump('BEGIN admin_orders.callbacks.init.onError');
				}
			}
		}, //callbacks

////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//actions are functions triggered by a user interaction, such as a click/tap.
//these are going the way of the do do, in favor of app events. new extensions should have few (if any) actions.
		a : {

			}, //Actions

////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//renderFormats are what is used to actually output data.
//on a data-bind, format: is equal to a renderformat. extension: tells the rendering engine where to look for the renderFormat.
//that way, two render formats named the same (but in different extensions) don't overwrite each other.
		renderFormats : {

			}, //renderFormats
////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//utilities are typically functions that are exected by an event or action.
//any functions that are recycled should be here.
		u : {
			welcomeRobot : function(botStr){
				_app.ext.quickstart.vars.cachedPageCount = 0;
				var request = {
					"_cmd" : "appSEOFetch"
					};
				request._tag = {
					'datapointer' : 'appSEOFetch',
					'callback' : function(rd){
						$.merge(_app.ext.seo_robots.vars.pages, _app.data[rd.datapointer]['@OBJECTS']);
						_app.ext.seo_robots.vars.pagesLoaded = true;
						}
					};
				_app.model.addDispatchToQ(request, 'immutable');
				_app.model.dispatchThis('immutable');

				return "1.0";
				}
			}, //u [utilities]

//app-events are added to an element through data-app-event="extensionName|functionName"
//right now, these are not fully supported, but they will be going forward.
//they're used heavily in the admin.html file.
//while no naming convention is stricly forced,
//when adding an event, be sure to do off('click.appEventName') and then on('click.appEventName') to ensure the same event is not double-added if app events were to get run again over the same template.
		e : {
			} //e [app Events]
		} //r object.
	return r;
	}<commit_msg>robot pages- better handling, products only<commit_after>/* **************************************************************

   Copyright 2013 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

************************************************************** */

//    !!! ->   TODO: replace 'username' in the line below with the merchants username.     <- !!!

var seo_robots = function(_app) {
	var r = {

	vars : {
		pages : [],
		pagesLoaded : false
		},

////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

	callbacks : {
//executed when extension is loaded. should include any validation that needs to occur.
		init : {
			onSuccess : function()	{
				var r = false; //return false if extension won't load for some reason (account config, dependencies, etc).

				if(_app.vars._robotGreeting){
					_app.ext.seo_robots.u.welcomeRobot(_app.vars._robotGreeting);
					}
				else {
					_robots.hello = _app.ext.seo_robots.u.welcomeRobot;
					}
				//Replace the _robots.next default functionality with some real stuff

				_robots.next = function(){
					if(_app.ext.seo_robots.vars.pagesLoaded){
						var infoObj = null;
						do {
							var p = _app.ext.seo_robots.vars.pages.splice(0,1)[0];

							if(typeof p == 'undefined'){
								_robots.status = function(){ return -1; }
								return false;
								}
							else {
								switch(p.type){
									case "pid":
										infoObj = {
											pageType : "product",
											pid : p.id
											};
										break;
									case "navcat" :
										infoObj = {
											pageType : "category",
											navcat : p.id
											};
										break;
									case "list" :
										dump("LIST "+p.id+" SKIPPED IN PAGE BUILDING");
										break;
									default :
										dump("Unrecognized pageInfo type: "+p.type+" full obj follows:");
										dump(p);
										break;
									}
								}
							}
						while(typeof infoObj == null);

						var status = 100;
						var hasRun = false;
						_robots.status = function(){
							if(hasRun){
								if(_app.ext.quickstart.vars.showContentFinished && _app.ext.quickstart.vars.showContentCompleteFired){
									status = 200;
									}
								else {
									status = 100;
									}
								}
							else {
								if(typeof p == 'string' && p.indexOf('#!') == 0){
									_app.ext.quickstart.vars.showContentFinished = false;
									_app.ext.quickstart.vars.showContentCompleteFired = false;
									window.location.hash = p;
									}
								else if(typeof p == 'object'){ //p is an object
									var infoObj = {};
									switch(p.type){
										case "pid":
											infoObj = {
												pageType : "product",
												pid : p.id
												};
											break;
										case "navcat" :
											infoObj = {
												pageType : "category",
												navcat : p.id
												};
											break;
										case "list" :
											dump("LIST "+p.id+" SKIPPED IN PAGE BUILDING");
											break;
										default :
											dump("Unrecognized pageInfo type: "+p.type+" full obj follows:");
											dump(p);
											break;
										}
									$('#globalMessaging').intervaledEmpty();
									showContent('',infoObj);
									}
								else {
									status = 404;
									}
								hasRun = true;
								}
							return status;
							}
						}
					else {
						_robots.status = function(){
							if(_app.ext.seo_robots.vars.pagesLoaded){
								return 204;
								}
							else {
								return 100;
								}
							}
						}
					}

				//if there is any functionality required for this extension to load, put it here. such as a check for async google, the FB object, etc. return false if dependencies are not present. don't check for other extensions.
				r = true;

				return r;
				},
			onError : function()	{
//errors will get reported for this callback as part of the extensions loading.  This is here for extra error handling purposes.
//you may or may not need it.
				_app.u.dump('BEGIN admin_orders.callbacks.init.onError');
				}
			}
		}, //callbacks

////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//actions are functions triggered by a user interaction, such as a click/tap.
//these are going the way of the do do, in favor of app events. new extensions should have few (if any) actions.
		a : {

			}, //Actions

////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//renderFormats are what is used to actually output data.
//on a data-bind, format: is equal to a renderformat. extension: tells the rendering engine where to look for the renderFormat.
//that way, two render formats named the same (but in different extensions) don't overwrite each other.
		renderFormats : {

			}, //renderFormats
////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//utilities are typically functions that are exected by an event or action.
//any functions that are recycled should be here.
		u : {
			welcomeRobot : function(botStr){
				_app.ext.quickstart.vars.cachedPageCount = 0;
				var request = {
					"_cmd" : "appSEOFetch"
					};
				request._tag = {
					'datapointer' : 'appSEOFetch',
					'callback' : function(rd){
						$.merge(_app.ext.seo_robots.vars.pages, _app.data[rd.datapointer]['@OBJECTS']);
						_app.ext.seo_robots.vars.pages = $.grep(_app.ext.seo_robots.vars.pages, function(e,i){
							if(typeof e == 'object' && e.type == 'pid'){
								return true;
								}
							else {
								return false;
								}
							});
						_app.ext.seo_robots.vars.pagesLoaded = true;
						}
					};
				_app.model.addDispatchToQ(request, 'immutable');
				_app.model.dispatchThis('immutable');

				return "1.0";
				}
			}, //u [utilities]

//app-events are added to an element through data-app-event="extensionName|functionName"
//right now, these are not fully supported, but they will be going forward.
//they're used heavily in the admin.html file.
//while no naming convention is stricly forced,
//when adding an event, be sure to do off('click.appEventName') and then on('click.appEventName') to ensure the same event is not double-added if app events were to get run again over the same template.
		e : {
			} //e [app Events]
		} //r object.
	return r;
	}<|endoftext|>


<commit_before>odoo.define('im_support.SupportMessage', function (require) {
"use strict";

var Message = require('mail.model.Message');

var session = require('web.session');

/**
 * This is a model for messages that are in the the support channel
 */
var SupportMessage = Message.extend({
    /**
     * @override
     * @param {Class} parent
     * @param {Object} data
     * @param {Array} [data.author_id=[]]
     */
    init: function (parent, data) {
        data.author_id = data.author_id || [];
        if (data.author_id !== this.call('mail_service', 'getOdoobotID')) {
            if (!data.author_id[0]) {
                // the author is the client
                data.author_id = [session.partner_id, session.name];
            } else {
                // the author is the operator
                // prevent from conflicting with partners of this instance
                data.author_id[0] = -1;
            }
        }
        this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @override
     * @returns {string}
     */
    getAvatarSource: function () {
        return '/mail/static/src/img/odoo_o.png';
    },
    /**
     * Overrides to prevent clicks on the Support operator from redirecting.
     *
     * @override
     * @returns {boolean}
     */
    shouldRedirectToAuthor: function () {
        return false;
    },
    /**
     * Overrides to prevent from calling the server for messages of the Support
     * channel (which are records on the Support database).
     *
     * @override
     * @returns {$.Promise}
     */
    toggleStarStatus: function () {
        return $.when();
    },
});

return SupportMessage;

});
<commit_msg>[FIX] im_support: support message init()<commit_after>odoo.define('im_support.SupportMessage', function (require) {
"use strict";

var Message = require('mail.model.Message');

var session = require('web.session');

/**
 * This is a model for messages that are in the the support channel
 */
var SupportMessage = Message.extend({
    /**
     * @override
     */
    init: function () {
        this._super.apply(this, arguments);
        if (this._serverAuthorID !== this.call('mail_service', 'getOdoobotID')) {
            if (!this._serverAuthorID[0]) {
                // the author is the client
                this._serverAuthorID = [session.partner_id, session.name];
            } else {
                // the author is the operator
                // prevent from conflicting with partners of this instance
                this._serverAuthorID[0] = -1;
            }
        }
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @override
     * @returns {string}
     */
    getAvatarSource: function () {
        return '/mail/static/src/img/odoo_o.png';
    },
    /**
     * Overrides to prevent clicks on the Support operator from redirecting.
     *
     * @override
     * @returns {boolean}
     */
    shouldRedirectToAuthor: function () {
        return false;
    },
    /**
     * Overrides to prevent from calling the server for messages of the Support
     * channel (which are records on the Support database).
     *
     * @override
     * @returns {$.Promise}
     */
    toggleStarStatus: function () {
        return $.when();
    },
});

return SupportMessage;

});
<|endoftext|>


<commit_before>/**
 * Schedules a routine execution using `requestAnimationFrame`
 * https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
 */
export default class AnimationFrameExecutor {
  execute(routine) {
    return new Promise(resolve => {
      requestAnimationFrame(() => resolve(routine()));
    });
  }
}
<commit_msg>make AnimationFrameExecutor working with queue<commit_after>/**
 * Schedules a routine execution using `requestAnimationFrame`
 * https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
 */
export default class AnimationFrameExecutor {
  constructor(targetFPS = 60) {
    this.maxDeadline = Math.floor(1000 / targetFPS);
  }

  execute(queue) {
    return new Promise(resolveQueue => {
      const deadline = new Deadline(this.maxDeadline);
      requestAnimationFrame(() => {
        while (deadline.timeRemaining() > 0 && queue.length > 0) {
          const task = queue.shift();
          task.run();
        }

        if (queue.length > 0) {
          return resolveQueue(this.execute(queue));
        }

        resolveQueue();
      });
    });
  }
}
<|endoftext|>


<commit_before>var htmlparser = require("htmlparser2");
var through = require('through2');
var gutil = require('gulp-util');

var result;
var counter;
var components;

var tags = ["!doctype", "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datalist", "dd", "del", "details", "dfn", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"];

var a2s = function (attribs) {
   attributeStrings = "";
   for (var key in attribs) {
      attributeStrings += " " + key + "=\"" + attribs[key] + "\"";
   }
   return attributeStrings;
};

function isComponent(string) {
   return (tags.indexOf(string.toLowerCase()) == -1)
};

var toBuf = function (string) {
   result.contents = Buffer.concat([result.contents, Buffer(string)]);
}

var parser = new htmlparser.Parser({
   onopentag: function(tagname, attribs){
      if(isComponent(tagname)){
         var id = "ReactComponent" + counter; counter++;
         components.push({id: id, name: tagname, att: attribs});
         toBuf("

");
      }
      else {
         toBuf("<" + tagname.toLowerCase() + a2s(attribs) + ">");
      }
   },

   ontext: function(text){
      toBuf(text)
   },

   oncomment: function (text) {
      toBuf("");
   },

   onprocessinginstruction: function (name, value) {
      toBuf("<" + value + ">")
   },

   onclosetag: function(tagname){
      if(tagname === "br" || isComponent(tagname)){
      }
      else if(tagname === "body"){
         if (components.length != 0) {
            toBuf("\n\n");
         }
         toBuf("");
      }
      else {
         toBuf("</" + tagname + ">");
      }
   }
}, {
   lowerCaseTags: false,
   recognizeSelfClosing: true,
});

module.exports = function () {
   counter = 0;
   components = [];

   return through.obj(function (chunk, enc, callback) {
      if (gutil.isNull(chunk)) {
         callback(null, chunk);
      }
      if (gutil.isStream(chunk)) {
         throw new gutil.PluginError(PLUGIN_NAME, 'Streams not supported.');
      }

      // isBuffer() == true
      result = chunk.clone();
      result.contents = new Buffer("");

      parser.write(chunk.contents.toString());

      this.push(result);
      callback()
   });
};
<commit_msg>bugfix: now accepts non-lowercase attributes<commit_after>var htmlparser = require("htmlparser2");
var through = require('through2');
var gutil = require('gulp-util');

var result;
var counter;
var components;

var tags = ["!doctype", "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datalist", "dd", "del", "details", "dfn", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"];

var a2s = function (attribs) {
   attributeStrings = "";
   for (var key in attribs) {
      attributeStrings += " " + key + "=\"" + attribs[key] + "\"";
   }
   return attributeStrings;
};

function isComponent(string) {
   return (tags.indexOf(string.toLowerCase()) == -1)
};

var toBuf = function (string) {
   result.contents = Buffer.concat([result.contents, Buffer(string)]);
}

var parser = new htmlparser.Parser({
   onopentag: function(tagname, attribs){
      if(isComponent(tagname)){
         var id = "ReactComponent" + counter; counter++;
         components.push({id: id, name: tagname, att: attribs});
         toBuf("

");
      }
      else {
         toBuf("<" + tagname.toLowerCase() + a2s(attribs) + ">");
      }
   },

   ontext: function(text){
      toBuf(text)
   },

   oncomment: function (text) {
      toBuf("");
   },

   onprocessinginstruction: function (name, value) {
      toBuf("<" + value + ">")
   },

   onclosetag: function(tagname){
      if(tagname === "br" || isComponent(tagname)){
      }
      else if(tagname === "body"){
         if (components.length != 0) {
            toBuf("\n\n");
         }
         toBuf("");
      }
      else {
         toBuf("</" + tagname + ">");
      }
   }
}, {
   lowerCaseTags: false,
   lowerCaseAttributeNames: false,
   recognizeSelfClosing: true,
});

module.exports = function () {
   counter = 0;
   components = [];

   return through.obj(function (chunk, enc, callback) {
      if (gutil.isNull(chunk)) {
         callback(null, chunk);
      }
      if (gutil.isStream(chunk)) {
         throw new gutil.PluginError(PLUGIN_NAME, 'Streams not supported.');
      }

      // isBuffer() == true
      result = chunk.clone();
      result.contents = new Buffer("");

      parser.write(chunk.contents.toString());

      this.push(result);
      callback()
   });
};
<|endoftext|>


<commit_before>'use strict';

/*  @jsig
    allowUnknownRequire: true,
    allowUnusedFunction: true,
    partialExport: true
*/

var esprima = require('esprima');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var resolve = require('resolve');
var process = global.process;

var Errors = require('./errors.js');
var HeaderFile = require('./header-file.js');
var parseJSigAST = require('./lib/read-jsig-ast.js').parseJSigAST;
var ProgramMeta = require('./meta.js');
var GlobalScope = require('./scope.js').GlobalScope;
var pretty = require('./lib/pretty-errors.js');
var serialize = require('../serialize.js');

var operatorsFile = path.join(
    __dirname, 'builtin-definitions', 'operators.hjs'
);
var es5File = path.join(
    __dirname, 'builtin-definitions', 'es5.hjs'
);
var isHeaderR = /\.hjs$/;
var BIN_HEADER = '#!/usr/bin/env node\n';

TypeChecker.compile = function compile(fileName, options) {
    var checker = new TypeChecker(fileName, options);
    checker.checkProgram();
    return checker;
};

module.exports = TypeChecker;

function TypeChecker(entryFile, options) {
    options = options || {};

    var entryFiles = null;
    if (typeof entryFile === 'string') {
        entryFiles = [entryFile];
    } else if (Array.isArray(entryFile)) {
        entryFiles = entryFile;
    } else {
        assert(false, 'entryFile must be string or array');
    }

    this.entryFiles = entryFiles;
    this.files = options.files || Object.create(null);
    this.basedir = process.cwd();

    this.globalScope = new GlobalScope(this);
    this.metas = Object.create(null);
    this.headerFiles = Object.create(null);

    this.definitions = Object.create(null);

    this.definitionsFolder = options.definitions || null;
    this.globalsFile = options.globalsFile || null;
    this.optin = options.optin || false;

    this.errors = [];
    this.traces = [];
    this.moduleExportsType = null;
    this.currentMeta = null;

    this.errorType = null;
}

TypeChecker.prototype.serializeType =
function serializeType(type, opts) {
    return serialize(type, opts);
};

TypeChecker.prototype.addError = function addError(err) {
    // console.trace('addError(' + err.type + ')');
    this.errors.push(err);
};

TypeChecker.prototype.addTrace = function addTrace(trace) {
    this.traces.push(trace);
};

TypeChecker.prototype.countErrors = function countErrors() {
    return this.errors.length;
};

TypeChecker.prototype.getErrors =
function getErrors() {
    return this.errors.slice();
};

TypeChecker.prototype.setErrors =
function setErrors(list) {
    this.errors = list;
};

TypeChecker.prototype.prettyPrintAllErrors =
function prettyPrintAllErrors() {
    return pretty.prettyPrintAllErrors(this);
};

TypeChecker.prototype.prettyPrintAllErrorsWithTrace =
function prettyPrintAllErrors() {
    return pretty.prettyPrintAllErrorsWithTrace(this);
};

TypeChecker.prototype.prettyPrintTraces =
function prettyPrintTraces() {
    return pretty.prettyPrintTraces(this);
};

TypeChecker.prototype.checkProgram =
function checkProgram() {
    this.loadLanguageIdentifiers();
    this.preloadDefinitions();
    this.preloadGlobals();

    var meta;
    for (var i = 0; i < this.entryFiles.length; i++) {
        meta = this.getOrCreateMeta(this.entryFiles[i]);
    }

    if (this.entryFiles.length === 1 && meta) {
        this.moduleExportsType = meta.moduleExportsType;
    }
};

TypeChecker.prototype.loadLanguageIdentifiers =
function loadLanguageIdentifiers() {
    var opHeaderFile = this.getOrCreateHeaderFile(operatorsFile);
    assert(this.errors.length === 0, 'must be no errors');
    assert(opHeaderFile, 'must be able to load operators');

    var assignments = opHeaderFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var a = assignments[i];
        this.globalScope._addOperator(a.identifier, a.typeExpression);
    }

    var es5HeaderFile = this.getOrCreateHeaderFile(es5File);
    assert(this.errors.length === 0, 'must be no errors');
    assert(es5HeaderFile, 'must be able to load es5');

    assignments = es5HeaderFile.getResolvedAssignments();

    for (i = 0; i < assignments.length; i++) {
        a = assignments[i];

        if (a.identifier === 'Error') {
            // Must know that in ES5 the new Error() constructor
            // brands the result as an Error instance.

            /*jsig ignore next: narrow type cannot narrow by string*/
            a.typeExpression.brand = 'Error';
        }

        this.globalScope._addVar(a.identifier, a.typeExpression);
    }

    var errType = es5HeaderFile.getToken('Error');
    /*jsig ignore next: need to implement narrow by assert()*/
    errType.brand = 'Error';
    this.errorType = errType;

    this.globalScope._addVirtualType(
        'TString', es5HeaderFile.getToken('TString')
    );
    this.globalScope._addVirtualType(
        'TNumber', es5HeaderFile.getToken('TNumber')
    );
    this.globalScope._addVirtualType(
        'TArray', es5HeaderFile.getToken('TArray')
    );
    this.globalScope._addVirtualType(
        'TObject', es5HeaderFile.getToken('TObject')
    );
    this.globalScope._addVirtualType(
        'TDate', es5HeaderFile.getToken('TDate')
    );
    this.globalScope._addVirtualType(
        'TFunction', es5HeaderFile.getToken('TFunction')
    );
};

TypeChecker.prototype.loadJavaScriptIntoIndexTable =
function loadJavaScriptIntoIndexTable(indexTable) {
    var before = this.errors.length;

    var es5HeaderFile = this.getOrCreateHeaderFile(es5File);
    assert(this.errors.length === before, 'must be no errors');
    assert(es5HeaderFile, 'must be able to load es5');

    es5HeaderFile.resolveReferences();
    indexTable['Error'] = es5HeaderFile.indexTable['Error'];
};

TypeChecker.prototype.tryReadHeaderFile =
function tryReadHeaderFile(primaryFileName) {
    if (!this.files[primaryFileName]) {
        primaryFileName = path.resolve(primaryFileName);
    }

    var headerFile;
    if (this.headerFiles[primaryFileName]) {
        headerFile = this.headerFiles[primaryFileName];
        if (headerFile.hasErrors()) {
            return true;
        }

        return true;
    }

    var source = this.files[primaryFileName];
    if (source) {
        return true;
    }

    if (fs.existsSync(primaryFileName)) {
        return true;
    }

    var secondaryFileName = getSecondaryFileName(primaryFileName);
    if (fs.existsSync(secondaryFileName)) {
        return true;
    }

    return false;
};

/*
    For each js + hjs file it can exist in two locations

    /foo/bar/x.js
    /foo/bar/x.hjs

    or

    /foo/bar/x.js
    /foo/bar/_types/x.hjs
*/
function getSecondaryFileName(primaryFileName) {
    var segments = primaryFileName.split(path.sep);
    var basename = segments[segments.length - 1];
    segments[segments.length - 1] = '_types';
    segments[segments.length] = basename;

    var secondaryFileName = segments.join(path.sep);
    return secondaryFileName;
}

TypeChecker.prototype._readAndParseHeaderFile =
function _readAndParseHeaderFile(source, fileName) {
    var res = parseJSigAST(source);
    if (res.error) {
        /*jsig ignore next: Support narrowing member expr? */
        res.error.fileName = fileName;
        this.addError(res.error);
        return null;
    }

    if (!res.value) {
        return null;
    }

    return res.value;
};

/* getOrCreateHeaderFile by headerFileName */
TypeChecker.prototype.getOrCreateHeaderFile =
function getOrCreateHeaderFile(
    primaryFileName, node, importSourceText, importFileName
) {
    if (!this.files[primaryFileName]) {
        primaryFileName = path.resolve(primaryFileName);
    }

    var headerFile;
    if (this.headerFiles[primaryFileName]) {
        headerFile = this.headerFiles[primaryFileName];
        if (headerFile.hasErrors()) {
            return null;
        }

        return headerFile;
    }

    var source = this.files[primaryFileName];
    if (!source) {
        if (!fs.existsSync(primaryFileName)) {
            var secondaryFileName = getSecondaryFileName(primaryFileName);

            if (fs.existsSync(secondaryFileName)) {
                primaryFileName = secondaryFileName;
            } else {
                this.addError(Errors.CouldNotFindHeaderFile({
                    fileName: importFileName,
                    otherFile: primaryFileName,
                    loc: node ? node.loc : null,
                    line: node ? node.loc.start.line : null,
                    source: importSourceText ? importSourceText : null
                }));
                return null;
            }
        }

        source = fs.readFileSync(primaryFileName, 'utf8');
    }

    return this._createHeaderFile(source, primaryFileName);
};

TypeChecker.prototype._createHeaderFile =
function _createHeaderFile(source, fileName) {
    var jsigAST = this._readAndParseHeaderFile(source, fileName);
    if (!jsigAST) {
        return null;
    }

    var headerFile = new HeaderFile(this, jsigAST, fileName, source);
    this.headerFiles[fileName] = headerFile;

    headerFile.resolveReferences();

    if (headerFile.hasErrors()) {
        for (var i = 0; i < headerFile.errors.length; i++) {
            headerFile.errors[i].fileName = fileName;
            this.addError(headerFile.errors[i]);
        }
        return null;
    }

    return headerFile;
};

function tryResolveSync(fileName, basedir) {
    var tuple = [null, null];

    /*eslint-disable no-restricted-syntax*/
    try {
        tuple[1] = resolve.sync(fileName, {
            basedir: basedir
        });
    } catch (err) {
        tuple[0] = err;
    }
    /*eslint-enable no-restricted-syntax*/

    return tuple;
}

function tryEsprimaParse(source) {
    var tuple = [null, null];

    /*eslint-disable no-restricted-syntax*/
    try {
        tuple[1] = esprima.parse(source, {
            loc: true,
            attachComment: true,
            comment: true
        });
    } catch (err) {
        tuple[0] = err;
    }
    /*eslint-enable no-restricted-syntax*/

    return tuple;
}

TypeChecker.prototype.getOrCreateMeta =
function getOrCreateMeta(fileName) {
    if (!this.files[fileName]) {
        var tuple = tryResolveSync(fileName, this.basedir);
        if (tuple[0]) {
            this.addError(Errors.CouldNotFindFile({
                fileName: fileName,
                origMessage: tuple[0].message
            }));
            return null;
        }

        fileName = tuple[1];
    }

    if (this.metas[fileName]) {
        return this.metas[fileName];
    }

    var source = this.files[fileName];
    if (!source) {
        source = fs.readFileSync(fileName, 'utf8');
    }

    // Hack: to support JSON
    if (path.extname(fileName) === '.json') {
        source = 'module.exports = ' + source;
    }

    // Munge source text if it has unix executable header
    if (source.indexOf(BIN_HEADER) === 0) {
        source = source.slice(BIN_HEADER.length);
    }

    tuple = tryEsprimaParse(source);
    if (tuple[0]) {
        var parseError = tuple[0];
        this.addError(Errors.CouldNotParseJavaScript({
            fileName: fileName,
            line: parseError.lineNumber,
            detail: parseError.description,
            charOffset: parseError.index,
            source: source
        }));
        return null;
    }

    var ast = tuple[1];

    var meta = new ProgramMeta(this, ast, fileName, source);
    this.metas[fileName] = meta;

    var previousMeta = this.currentMeta;
    this.currentMeta = meta;
    meta.verify();
    this.currentMeta = previousMeta;

    return meta;
};

TypeChecker.prototype.getDefinition =
function getDefinition(id) {
    return this.definitions[id];
};

TypeChecker.prototype._addDefinition =
function _addDefinition(id, typeDefn) {
    var token = {
        type: 'external-definition',
        defn: typeDefn
    };
    this.definitions[id] = token;
    return token;
};

TypeChecker.prototype.getDefinitionFilePath =
function getDefinitionFilePath(packageName) {
    if (!this.definitionsFolder) {
        return null;
    }

    return path.join(
        this.definitionsFolder, packageName + '.hjs'
    );
};

TypeChecker.prototype.preloadDefinitions =
function preloadDefinitions() {
    if (!this.definitionsFolder) {
        return;
    }

    var files = fs.readdirSync(this.definitionsFolder);
    for (var i = 0; i < files.length; i++) {
        var fileName = path.join(this.definitionsFolder, files[i]);
        if (!isHeaderR.test(fileName)) {
            continue;
        }

        var headerFile = this.getOrCreateHeaderFile(fileName);
        if (!headerFile) {
            continue;
        }

        var assignments = headerFile.getResolvedAssignments();
        for (var j = 0; j < assignments.length; j++) {
            var a = assignments[j];
            this._addDefinition(a.identifier, a.typeExpression);
        }
    }
};

TypeChecker.prototype.preloadGlobals =
function preloadGlobals() {
    if (!this.globalsFile) {
        return;
    }

    var headerFile = this.getOrCreateHeaderFile(this.globalsFile);
    if (!headerFile) {
        return;
    }

    var assignments = headerFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var a = assignments[i];
        this.globalScope._addVar(a.identifier, a.typeExpression);
    }
};
<commit_msg>checker: refactor tuple usage to be type safe<commit_after>'use strict';

/*  @jsig
    allowUnknownRequire: true,
    allowUnusedFunction: true,
    partialExport: true
*/

var esprima = require('esprima');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var resolve = require('resolve');
var process = global.process;

var Errors = require('./errors.js');
var HeaderFile = require('./header-file.js');
var parseJSigAST = require('./lib/read-jsig-ast.js').parseJSigAST;
var ProgramMeta = require('./meta.js');
var GlobalScope = require('./scope.js').GlobalScope;
var pretty = require('./lib/pretty-errors.js');
var serialize = require('../serialize.js');

var operatorsFile = path.join(
    __dirname, 'builtin-definitions', 'operators.hjs'
);
var es5File = path.join(
    __dirname, 'builtin-definitions', 'es5.hjs'
);
var isHeaderR = /\.hjs$/;
var BIN_HEADER = '#!/usr/bin/env node\n';

TypeChecker.compile = function compile(fileName, options) {
    var checker = new TypeChecker(fileName, options);
    checker.checkProgram();
    return checker;
};

module.exports = TypeChecker;

function TypeChecker(entryFile, options) {
    options = options || {};

    var entryFiles = null;
    if (typeof entryFile === 'string') {
        entryFiles = [entryFile];
    } else if (Array.isArray(entryFile)) {
        entryFiles = entryFile;
    } else {
        assert(false, 'entryFile must be string or array');
    }

    this.entryFiles = entryFiles;
    this.files = options.files || Object.create(null);
    this.basedir = process.cwd();

    this.globalScope = new GlobalScope(this);
    this.metas = Object.create(null);
    this.headerFiles = Object.create(null);

    this.definitions = Object.create(null);

    this.definitionsFolder = options.definitions || null;
    this.globalsFile = options.globalsFile || null;
    this.optin = options.optin || false;

    this.errors = [];
    this.traces = [];
    this.moduleExportsType = null;
    this.currentMeta = null;

    this.errorType = null;
}

TypeChecker.prototype.serializeType =
function serializeType(type, opts) {
    return serialize(type, opts);
};

TypeChecker.prototype.addError = function addError(err) {
    // console.trace('addError(' + err.type + ')');
    this.errors.push(err);
};

TypeChecker.prototype.addTrace = function addTrace(trace) {
    this.traces.push(trace);
};

TypeChecker.prototype.countErrors = function countErrors() {
    return this.errors.length;
};

TypeChecker.prototype.getErrors =
function getErrors() {
    return this.errors.slice();
};

TypeChecker.prototype.setErrors =
function setErrors(list) {
    this.errors = list;
};

TypeChecker.prototype.prettyPrintAllErrors =
function prettyPrintAllErrors() {
    return pretty.prettyPrintAllErrors(this);
};

TypeChecker.prototype.prettyPrintAllErrorsWithTrace =
function prettyPrintAllErrors() {
    return pretty.prettyPrintAllErrorsWithTrace(this);
};

TypeChecker.prototype.prettyPrintTraces =
function prettyPrintTraces() {
    return pretty.prettyPrintTraces(this);
};

TypeChecker.prototype.checkProgram =
function checkProgram() {
    this.loadLanguageIdentifiers();
    this.preloadDefinitions();
    this.preloadGlobals();

    var meta;
    for (var i = 0; i < this.entryFiles.length; i++) {
        meta = this.getOrCreateMeta(this.entryFiles[i]);
    }

    if (this.entryFiles.length === 1 && meta) {
        this.moduleExportsType = meta.moduleExportsType;
    }
};

TypeChecker.prototype.loadLanguageIdentifiers =
function loadLanguageIdentifiers() {
    var opHeaderFile = this.getOrCreateHeaderFile(operatorsFile);
    assert(this.errors.length === 0, 'must be no errors');
    assert(opHeaderFile, 'must be able to load operators');

    var assignments = opHeaderFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var a = assignments[i];
        this.globalScope._addOperator(a.identifier, a.typeExpression);
    }

    var es5HeaderFile = this.getOrCreateHeaderFile(es5File);
    assert(this.errors.length === 0, 'must be no errors');
    assert(es5HeaderFile, 'must be able to load es5');

    assignments = es5HeaderFile.getResolvedAssignments();

    for (i = 0; i < assignments.length; i++) {
        a = assignments[i];

        if (a.identifier === 'Error') {
            // Must know that in ES5 the new Error() constructor
            // brands the result as an Error instance.

            /*jsig ignore next: narrow type cannot narrow by string*/
            a.typeExpression.brand = 'Error';
        }

        this.globalScope._addVar(a.identifier, a.typeExpression);
    }

    var errType = es5HeaderFile.getToken('Error');
    /*jsig ignore next: need to implement narrow by assert()*/
    errType.brand = 'Error';
    this.errorType = errType;

    this.globalScope._addVirtualType(
        'TString', es5HeaderFile.getToken('TString')
    );
    this.globalScope._addVirtualType(
        'TNumber', es5HeaderFile.getToken('TNumber')
    );
    this.globalScope._addVirtualType(
        'TArray', es5HeaderFile.getToken('TArray')
    );
    this.globalScope._addVirtualType(
        'TObject', es5HeaderFile.getToken('TObject')
    );
    this.globalScope._addVirtualType(
        'TDate', es5HeaderFile.getToken('TDate')
    );
    this.globalScope._addVirtualType(
        'TFunction', es5HeaderFile.getToken('TFunction')
    );
};

TypeChecker.prototype.loadJavaScriptIntoIndexTable =
function loadJavaScriptIntoIndexTable(indexTable) {
    var before = this.errors.length;

    var es5HeaderFile = this.getOrCreateHeaderFile(es5File);
    assert(this.errors.length === before, 'must be no errors');
    assert(es5HeaderFile, 'must be able to load es5');

    es5HeaderFile.resolveReferences();
    indexTable['Error'] = es5HeaderFile.indexTable['Error'];
};

TypeChecker.prototype.tryReadHeaderFile =
function tryReadHeaderFile(primaryFileName) {
    if (!this.files[primaryFileName]) {
        primaryFileName = path.resolve(primaryFileName);
    }

    var headerFile;
    if (this.headerFiles[primaryFileName]) {
        headerFile = this.headerFiles[primaryFileName];
        if (headerFile.hasErrors()) {
            return true;
        }

        return true;
    }

    var source = this.files[primaryFileName];
    if (source) {
        return true;
    }

    if (fs.existsSync(primaryFileName)) {
        return true;
    }

    var secondaryFileName = getSecondaryFileName(primaryFileName);
    if (fs.existsSync(secondaryFileName)) {
        return true;
    }

    return false;
};

/*
    For each js + hjs file it can exist in two locations

    /foo/bar/x.js
    /foo/bar/x.hjs

    or

    /foo/bar/x.js
    /foo/bar/_types/x.hjs
*/
function getSecondaryFileName(primaryFileName) {
    var segments = primaryFileName.split(path.sep);
    var basename = segments[segments.length - 1];
    segments[segments.length - 1] = '_types';
    segments[segments.length] = basename;

    var secondaryFileName = segments.join(path.sep);
    return secondaryFileName;
}

TypeChecker.prototype._readAndParseHeaderFile =
function _readAndParseHeaderFile(source, fileName) {
    var res = parseJSigAST(source);
    if (res.error) {
        /*jsig ignore next: Support narrowing member expr? */
        res.error.fileName = fileName;
        this.addError(res.error);
        return null;
    }

    if (!res.value) {
        return null;
    }

    return res.value;
};

/* getOrCreateHeaderFile by headerFileName */
TypeChecker.prototype.getOrCreateHeaderFile =
function getOrCreateHeaderFile(
    primaryFileName, node, importSourceText, importFileName
) {
    if (!this.files[primaryFileName]) {
        primaryFileName = path.resolve(primaryFileName);
    }

    var headerFile;
    if (this.headerFiles[primaryFileName]) {
        headerFile = this.headerFiles[primaryFileName];
        if (headerFile.hasErrors()) {
            return null;
        }

        return headerFile;
    }

    var source = this.files[primaryFileName];
    if (!source) {
        if (!fs.existsSync(primaryFileName)) {
            var secondaryFileName = getSecondaryFileName(primaryFileName);

            if (fs.existsSync(secondaryFileName)) {
                primaryFileName = secondaryFileName;
            } else {
                this.addError(Errors.CouldNotFindHeaderFile({
                    fileName: importFileName,
                    otherFile: primaryFileName,
                    loc: node ? node.loc : null,
                    line: node ? node.loc.start.line : null,
                    source: importSourceText ? importSourceText : null
                }));
                return null;
            }
        }

        source = fs.readFileSync(primaryFileName, 'utf8');
    }

    return this._createHeaderFile(source, primaryFileName);
};

TypeChecker.prototype._createHeaderFile =
function _createHeaderFile(source, fileName) {
    var jsigAST = this._readAndParseHeaderFile(source, fileName);
    if (!jsigAST) {
        return null;
    }

    var headerFile = new HeaderFile(this, jsigAST, fileName, source);
    this.headerFiles[fileName] = headerFile;

    headerFile.resolveReferences();

    if (headerFile.hasErrors()) {
        for (var i = 0; i < headerFile.errors.length; i++) {
            headerFile.errors[i].fileName = fileName;
            this.addError(headerFile.errors[i]);
        }
        return null;
    }

    return headerFile;
};

function tryResolveSync(fileName, basedir) {
    var tuple = null;

    /*eslint-disable no-restricted-syntax*/
    try {
        tuple = [null, resolve.sync(fileName, {
            basedir: basedir
        })];
    } catch (err) {
        tuple = [err, null];
    }
    /*eslint-enable no-restricted-syntax*/

    return tuple;
}

function tryEsprimaParse(source) {
    var tuple = null;

    /*eslint-disable no-restricted-syntax*/
    try {
        tuple = [null, esprima.parse(source, {
            loc: true,
            attachComment: true,
            comment: true
        })];
    } catch (err) {
        tuple = [err, null];
    }
    /*eslint-enable no-restricted-syntax*/

    return tuple;
}

TypeChecker.prototype.getOrCreateMeta =
function getOrCreateMeta(fileName) {
    if (!this.files[fileName]) {
        var tuple = tryResolveSync(fileName, this.basedir);
        if (tuple[0]) {
            this.addError(Errors.CouldNotFindFile({
                fileName: fileName,
                origMessage: tuple[0].message
            }));
            return null;
        }

        fileName = tuple[1];
    }

    if (this.metas[fileName]) {
        return this.metas[fileName];
    }

    var source = this.files[fileName];
    if (!source) {
        source = fs.readFileSync(fileName, 'utf8');
    }

    // Hack: to support JSON
    if (path.extname(fileName) === '.json') {
        source = 'module.exports = ' + source;
    }

    // Munge source text if it has unix executable header
    if (source.indexOf(BIN_HEADER) === 0) {
        source = source.slice(BIN_HEADER.length);
    }

    tuple = tryEsprimaParse(source);
    if (tuple[0]) {
        var parseError = tuple[0];
        this.addError(Errors.CouldNotParseJavaScript({
            fileName: fileName,
            line: parseError.lineNumber,
            detail: parseError.description,
            charOffset: parseError.index,
            source: source
        }));
        return null;
    }

    var ast = tuple[1];

    var meta = new ProgramMeta(this, ast, fileName, source);
    this.metas[fileName] = meta;

    var previousMeta = this.currentMeta;
    this.currentMeta = meta;
    meta.verify();
    this.currentMeta = previousMeta;

    return meta;
};

TypeChecker.prototype.getDefinition =
function getDefinition(id) {
    return this.definitions[id];
};

TypeChecker.prototype._addDefinition =
function _addDefinition(id, typeDefn) {
    var token = {
        type: 'external-definition',
        defn: typeDefn
    };
    this.definitions[id] = token;
    return token;
};

TypeChecker.prototype.getDefinitionFilePath =
function getDefinitionFilePath(packageName) {
    if (!this.definitionsFolder) {
        return null;
    }

    return path.join(
        this.definitionsFolder, packageName + '.hjs'
    );
};

TypeChecker.prototype.preloadDefinitions =
function preloadDefinitions() {
    if (!this.definitionsFolder) {
        return;
    }

    var files = fs.readdirSync(this.definitionsFolder);
    for (var i = 0; i < files.length; i++) {
        var fileName = path.join(this.definitionsFolder, files[i]);
        if (!isHeaderR.test(fileName)) {
            continue;
        }

        var headerFile = this.getOrCreateHeaderFile(fileName);
        if (!headerFile) {
            continue;
        }

        var assignments = headerFile.getResolvedAssignments();
        for (var j = 0; j < assignments.length; j++) {
            var a = assignments[j];
            this._addDefinition(a.identifier, a.typeExpression);
        }
    }
};

TypeChecker.prototype.preloadGlobals =
function preloadGlobals() {
    if (!this.globalsFile) {
        return;
    }

    var headerFile = this.getOrCreateHeaderFile(this.globalsFile);
    if (!headerFile) {
        return;
    }

    var assignments = headerFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var a = assignments[i];
        this.globalScope._addVar(a.identifier, a.typeExpression);
    }
};
<|endoftext|>