docs: public Context getContextAfterDynamicValue() {
      // TODO: If the context is JS, perhaps this should return JsFollowingSlash.UNKNOWN. Right now
      // we assume that the dynamic value is also an expression, but JsFollowingSlash.UNKNOWN would
      // account for things that end in semicolons (since the next slash could be either a regex OR a
      // division op).
      if (state == HtmlContext.JS) {
        switch (slashType) {
          case DIV_OP:
          case UNKNOWN:
            return this;
          case REGEX:
            return derive(JsFollowingSlash.DIV_OP);
          case NONE:
            throw new IllegalStateException(slashType.name());
        }
      } else if (state == HtmlContext.HTML_BEFORE_OPEN_TAG_NAME
          || state == HtmlContext.HTML_BEFORE_CLOSE_TAG_NAME) {
        // We assume ElementType.NORMAL, because filterHtmlElementName filters dangerous tag names.
        return toBuilder()
            .withState(HtmlContext.HTML_TAG_NAME)
            .withElType(ElementType.NORMAL)
            .build();
      } else if (state == HtmlContext.HTML_TAG) {
        // To handle a substitution that starts an attribute name <tag {$attrName}=...>
        return toBuilder()
            .withState(HtmlContext.HTML_ATTRIBUTE_NAME)
            .withAttrType(AttributeType.PLAIN_TEXT)
            .build();
      } else if (uriPart == UriPart.START) {
        if (uriType == UriType.TRUSTED_RESOURCE) {
          return derive(UriPart.AUTHORITY_OR_PATH);
        }
        return derive(UriPart.MAYBE_VARIABLE_SCHEME);
      }
      return this;
    }