docs: static Optional<Context> union(Context a, Context b) {
      // NOTE: Avoid the temptation to return early; instead, rely on the equals() check at the end
      // to ensure all properties match. Checking equals() at the end ensures that when new
      // properties are added, they get checked automatically.
  
      // Try to reconcile each property one-by-one.
      if (a.slashType != b.slashType) {
        a = a.derive(JsFollowingSlash.UNKNOWN);
        b = b.derive(JsFollowingSlash.UNKNOWN);
      }
  
      if (a.uriPart != b.uriPart) {
        UriPart unionedUriPart = unionUriParts(a.uriPart, b.uriPart);
        a = a.derive(unionedUriPart);
        b = b.derive(unionedUriPart);
      }
  
      if (a.state != b.state) {
        // Order by state so that we don't have to duplicate tests below.
        if (a.state.compareTo(b.state) > 0) {
          Context swap = a;
          a = b;
          b = swap;
        }
  
        // consider <div foo=bar{if $p} onclick=foo(){/if} x=y>
        // if both branches need a space or tag end to complete, and their states aren't compatible
        // switch to TAG_NAME to require a space
        if (a.delimType == AttributeEndDelimiter.SPACE_OR_TAG_END
            && b.delimType == AttributeEndDelimiter.SPACE_OR_TAG_END
            && a.state != b.state) {
          // we need to switch to a state that requires a space
          // TODO(lukes): given this usecase, HTML_TAG_NAME is poorly named, consider
          // AFTER_TAG_OR_UNQUOTED_ATTR?  maybe just HTML_TAG_NEEDS_SPACE
          a = a.toBuilder().withState(HtmlContext.HTML_TAG_NAME).withoutAttrContext().build();
          // The next block will clean up b.
        }
  
        // consider <input{if $foo} disabled{/if}> or <input{$if foo} disabled=true{/if}
        // if we start in a tag name and end in an attribute name or value, assume we are still in a
        // tag name.
        if (a.state == HtmlContext.HTML_TAG_NAME) {
          if (b.state == HtmlContext.HTML_ATTRIBUTE_NAME
              || b.delimType == AttributeEndDelimiter.SPACE_OR_TAG_END) {
            // clear attributes from a also,  this is counterintuitive because tagnames shouldn't have
            // attrccontext at all, but prior reconciliation of slashtype may have added one.  so
            // clear it.
            a = a.toBuilder().withoutAttrContext().build();
            b = b.toBuilder().withState(HtmlContext.HTML_TAG_NAME).withoutAttrContext().build();
          }
        }
  
        // If we start in a tag name and end between attributes, then treat us as between attributes.
        // This handles <b{if $bool} attrName="value"{/if}>.
        if (a.state == HtmlContext.HTML_TAG_NAME && b.state == HtmlContext.HTML_TAG) {
          // Note we only change the state; if the element type is different, we don't want it to
          // join.
          // TODO(gboyer): The withoutAttrContext() doesn't make any sense, since HTML_TAG_NAME can't
          // have an attribute context.
          a = a.toBuilder().withState(HtmlContext.HTML_TAG).withoutAttrContext().build();
        }
  
        if (a.state == HtmlContext.HTML_TAG) {
          // If one branch is waiting for an attribute name, and the other is waiting for an equal
          // sign before an attribute value OR the end of an unquoted attribute value, then commit to
          // the view that the attribute name was a valueless attribute and transition to a state
          // waiting for another attribute name or the end of a tag.
          // Examples:
          // - state == HTML_ATTRIBUTE_NAME: <input {if $x}disabled{/if}
          // - delimType == SPACE_TAG_OR_END: <input {if $x}type=text{/if}
          if (b.state == HtmlContext.HTML_ATTRIBUTE_NAME
              || b.delimType == AttributeEndDelimiter.SPACE_OR_TAG_END) {
            // TODO(gboyer): do we need to require a space before any new attribute name after an
            // unquoted attribute?
            b = b.toBuilder().withState(HtmlContext.HTML_TAG).withoutAttrContext().build();
          }
        }
      }
  
      return a.equals(b) ? Optional.of(a) : Optional.absent();
    }