Add documentation and light refactoring to linkification code

Change-Id: Ia5f70e44d3b35130376ef2961ea88511d85279d9
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 2c45311..22e14e9 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -43,7 +43,7 @@
       '_contentOrConfigChanged(content, config)',
     ],
 
-    _contentChanged: function(content) {
+    _contentChanged(content) {
       // In the case where the config may not be set (perhaps due to the
       // request for it still being in flight), set the content anyway to
       // prevent waiting on the config to display the text.
@@ -51,22 +51,19 @@
       this.$.output.textContent = content;
     },
 
-    _contentOrConfigChanged: function(content, config) {
+    /**
+     * Because either the source text or the linkification config has changed,
+     * the content should be re-parsed.
+     * @param {string|null|undefined} content The raw, un-linkified source
+     *     string to parse.
+     * @param {Object|null|undefined} config The server config specifying
+     *     commentLink patterns
+     */
+    _contentOrConfigChanged(content, config) {
       var output = Polymer.dom(this.$.output);
       output.textContent = '';
-      var parser = new GrLinkTextParser(
-          config, function(text, href, fragment) {
-        if (href) {
-          var a = document.createElement('a');
-          a.href = href;
-          a.textContent = text;
-          a.target = '_blank';
-          a.rel = 'noopener';
-          output.appendChild(a);
-        } else if (fragment) {
-          output.appendChild(fragment);
-        }
-      }, this.removeZeroWidthSpace);
+      var parser = new GrLinkTextParser(config,
+          this._handleParseResult.bind(this), this.removeZeroWidthSpace);
       parser.parse(content);
 
       // Ensure that links originating from HTML commentlink configs open in a
@@ -76,5 +73,31 @@
         anchor.setAttribute('rel', 'noopener');
       });
     },
+
+    /**
+     * This method is called when the GrLikTextParser emits a partial result
+     * (used as the "callback" parameter). It will be called in either of two
+     * ways:
+     * - To create a link: when called with `text` and `href` arguments, a link
+     *   element should be created and attached to the resulting DOM.
+     * - To attach an arbitrary fragment: when called with only the `fragment`
+     *   argument, the fragment should be attached to the resulting DOM as is.
+     * @param {string|null} text
+     * @param {string|null} href
+     * @param  {DocumentFragment|undefined} fragment
+     */
+    _handleParseResult(text, href, fragment) {
+      var output = Polymer.dom(this.$.output);
+      if (href) {
+        var a = document.createElement('a');
+        a.href = href;
+        a.textContent = text;
+        a.target = '_blank';
+        a.rel = 'noopener';
+        output.appendChild(a);
+      } else if (fragment) {
+        output.appendChild(fragment);
+      }
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 81074be..8b49ca0 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -17,8 +17,35 @@
 (function() {
   'use strict';
 
+  const Defs = {};
+
+  /**
+   * @typedef {{
+   *    html: Node,
+   *    position: number,
+   *    length: number,
+   * }}
+   */
+  Defs.CommentLinkItem;
+
+  /**
+   * Pattern describing URLs with supported protocols.
+   * @type {RegExp}
+   */
   const URL_PROTOCOL_PATTERN = /^(https?:\/\/|mailto:)/;
 
+  /**
+   * Construct a parser for linkifying text. Will linkify plain URLs that appear
+   * in the text as well as custom links if any are specified in the linkConfig
+   * parameter.
+   * @param {Object|null|undefined} linkConfig Comment links as specified by the
+   *     commentlinks field on a project config.
+   * @param {Function} callback The callback to be fired when an intermediate
+   *     parse result is emitted. The callback is passed text and href strings if
+   *     a link is to be created, or a document fragment otherwise.
+   * @param {boolean|undefined} opt_removeZeroWidthSpace If true, zero-width
+   *     spaces will be removed from R=<email> and CC=<email> expressions.
+   */
   function GrLinkTextParser(linkConfig, callback, opt_removeZeroWidthSpace) {
     this.linkConfig = linkConfig;
     this.callback = callback;
@@ -26,13 +53,24 @@
     Object.preventExtensions(this);
   }
 
+  /**
+   * Emit a callback to create a link element.
+   * @param {string} text The text of the link.
+   * @param {string} href The URL to use as the href of the link.
+   */
   GrLinkTextParser.prototype.addText = function(text, href) {
-    if (!text) {
-      return;
-    }
+    if (!text) { return; }
     this.callback(text, href);
   };
 
+  /**
+   * Given the source text and a list of CommentLinkItem objects that were
+   * generated by the commentlinks config, emit parsing callbacks.
+   * @param {string} text The chuml of source text over which the outputArray
+   *     items range.
+   * @param {!Array<Defs.CommentLinkItem>} outputArray The list of items to add
+   *     resulting from commentlink matches.
+   */
   GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
     this.sortArrayReverse(outputArray);
     var fragment = document.createDocumentFragment();
@@ -62,10 +100,34 @@
     this.callback(null, null, fragment);
   };
 
+  /**
+   * Sort the given array of CommentLinkItems such that the positions are in
+   * reverse order.
+   * @param {!Array<Defs.CommentLinkItem>} outputArray
+   */
   GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
-    outputArray.sort(function(a, b) {return b.position - a.position});
+    outputArray.sort((a, b) => b.position - a.position);
   };
 
+  /**
+   * Create a CommentLinkItem and append it to the given output array. This
+   * method can be called in either of two ways:
+   * - With `text` and `href` parameters provided, and the `html` parameter
+   *   passed as `null`. In this case, the new CommentLinkItem will be a link
+   *   element with the given text and href value.
+   * - With the `html` paremeter provided, and the `text` and `href` parameters
+   *   passed as `null`. In this case, the string of HTML will be parsed and the
+   *   first resulting node will be used as the resulting content.
+   * @param {string|null} text The text to use if creating a link.
+   * @param {string|null} href The href to use as the URL if creating a link.
+   * @param {string|null} html The html to parse and use as the result.
+   * @param {number} position The position inside the source text where the item
+   *     starts.
+   * @param {number} length The number of characters in the source text
+   *     represented by the item.
+   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   *     new item is to be appended.
+   */
   GrLinkTextParser.prototype.addItem =
       function(text, href, html, position, length, outputArray) {
     var htmlOutput = '';
@@ -95,23 +157,47 @@
     });
   };
 
+  /**
+   * Create a CommentLinkItem for a link and append it to the given output
+   * array.
+   * @param {string|null} text The text for the link.
+   * @param {string|null} href The href to use as the URL of the link.
+   * @param {number} position The position inside the source text where the link
+   *     starts.
+   * @param {number} length The number of characters in the source text
+   *     represented by the link.
+   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   *     new item is to be appended.
+   */
   GrLinkTextParser.prototype.addLink =
       function(text, href, position, length, outputArray) {
-    if (!text) {
-      return;
-    }
-    if (!this.hasOverlap(position, length, outputArray)) {
-      this.addItem(text, href, null, position, length, outputArray);
-    }
+    if (!text || this.hasOverlap(position, length, outputArray)) { return; }
+    this.addItem(text, href, null, position, length, outputArray);
   };
 
+  /**
+   * Create a CommentLinkItem specified by an HTMl string and append it to the
+   * given output array.
+   * @param {string|null} html The html to parse and use as the result.
+   * @param {number} position The position inside the source text where the item
+   *     starts.
+   * @param {number} length The number of characters in the source text
+   *     represented by the item.
+   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   *     new item is to be appended.
+   */
   GrLinkTextParser.prototype.addHTML =
       function(html, position, length, outputArray) {
-    if (!this.hasOverlap(position, length, outputArray)) {
-      this.addItem(null, null, html, position, length, outputArray);
-    }
+    if (this.hasOverlap(position, length, outputArray)) { return; }
+    this.addItem(null, null, html, position, length, outputArray);
   };
 
+  /**
+   * Does the given range overlap with anything already in the item list.
+   * @param {number} position
+   * @param {number} length
+   * @param {!Array<Defs.CommentLinkItem>} outputArray
+   */
   GrLinkTextParser.prototype.hasOverlap =
       function(position, length, outputArray) {
     var endPosition = position + length;
@@ -127,12 +213,27 @@
     return false;
   };
 
+  /**
+   * Parse the given source text and emit callbacks for the items that are
+   * parsed.
+   * @param {string} text
+   */
   GrLinkTextParser.prototype.parse = function(text) {
     linkify(text, {
       callback: this.parseChunk.bind(this),
     });
   };
 
+  /**
+   * Callback that is pased into the linkify function. ba-linkify will call this
+   * method in either of two ways:
+   * - With both a `text` and `href` parameter provided: this indicates that
+   *   ba-linkify has found a plain URL and wants it linkified.
+   * - With only a `text` parameter provided: this represents the non-link
+   *   content that lies between the links the library has found.
+   * @param {string} text
+   * @param {string|null|undefined} href
+   */
   GrLinkTextParser.prototype.parseChunk = function(text, href) {
     // TODO(wyatta) switch linkify sequence, see issue 5526.
     if (this.removeZeroWidthSpace) {
@@ -147,10 +248,19 @@
     if (href && URL_PROTOCOL_PATTERN.test(href)) {
       this.addText(text, href);
     } else {
+      // For the sections of text that lie between the links found by
+      // ba-linkify, we search for the project-config-specified link patterns.
       this.parseLinks(text, this.linkConfig);
     }
   };
 
+  /**
+   * Walk over the given source text to find matches for comemntlink patterns
+   * and emit parse result callbacks.
+   * @param {string} text The raw source text.
+   * @param {Object|null|undefined} patterns A comment links specification
+   *   object.
+   */
   GrLinkTextParser.prototype.parseLinks = function(text, patterns) {
     // The outputArray is used to store all of the matches found for all patterns.
     var outputArray = [];
@@ -158,10 +268,8 @@
       if (patterns[p].enabled != null && patterns[p].enabled == false) {
         continue;
       }
-      // PolyGerrit doesn't use hash-based navigation like GWT.
+      // PolyGerrit doesn't use hash-based navigation like the GWT UI.
       // Account for this.
-      // TODO(andybons): Support Gerrit being served from a base other than /,
-      // e.g. https://git.eclipse.org/r/
       if (patterns[p].html) {
         patterns[p].html =
             patterns[p].html.replace(/<a href=\"#\//g, '<a href="/');
@@ -217,4 +325,4 @@
   };
 
   window.GrLinkTextParser = GrLinkTextParser;
-})();
\ No newline at end of file
+})();