docs: @Override
    protected void visitHtmlAttributeNode(HtmlAttributeNode node) {
      // Skip attributes that do not have a value.
      if (!node.hasValue()) {
        return;
      }
      SourceLocation insertionLocation = node.getSourceLocation();
      for (FunctionNode function : SoyTreeUtils.getAllNodesOfType(node, FunctionNode.class)) {
        if (!(function.getSoyFunction() instanceof LoggingFunction)) {
          continue;
        }
        FunctionNode funcNode =
            new FunctionNode(
                Identifier.create(VeLogJsSrcLoggingFunction.NAME, insertionLocation),
                VeLogJsSrcLoggingFunction.INSTANCE,
                insertionLocation);
        funcNode.addChild(
            new StringNode(function.getFunctionName(), QuoteStyle.SINGLE, insertionLocation));
        funcNode.addChild(new ListLiteralNode(function.getChildren(), insertionLocation));
        StandaloneNode attributeName = node.getChild(0);
        if (attributeName instanceof RawTextNode) {
          // If attribute name is a plain text, directly pass it as a function argument.
          funcNode.addChild(
              new StringNode(
                  ((RawTextNode) attributeName).getRawText(), QuoteStyle.SINGLE, insertionLocation));
        } else {
          // Otherwise wrap the print node or call node into a let block, and use the let variable
          // as a function argument.
          String varName = "$soy_logging_function_attribute_" + node.getId();
          LetContentNode letNode =
              LetContentNode.forVariable(
                  nodeIdGen.genId(),
                  attributeName.getSourceLocation(),
                  varName,
                  attributeName.getSourceLocation(),
                  null);
          // Adds a let var which references to the original attribute name, and move the name to
          // the let block.
          node.replaceChild(
              attributeName,
              new PrintNode(
                  nodeIdGen.genId(),
                  insertionLocation,
                  /* isImplicit= */ true,
                  /* expr= */ new VarRefNode(
                      letNode.getVar().name(), insertionLocation, letNode.getVar()),
                  /* attributes= */ ImmutableList.of(),
                  ErrorReporter.exploding()));
          letNode.addChild(attributeName);
          node.getParent().addChild(node.getParent().getChildIndex(node), letNode);
          funcNode.addChild(
              new VarRefNode(letNode.getVar().name(), insertionLocation, letNode.getVar()));
        }
        PrintNode loggingFunctionAttribute =
            new PrintNode(
                nodeIdGen.genId(),
                insertionLocation,
                /* isImplicit= */ true,
                /* expr= */ funcNode,
                /* attributes= */ ImmutableList.of(),
                ErrorReporter.exploding());
        // Append the logging function attribute to its parent
        int appendIndex = node.getParent().getChildIndex(node) + 1;
        node.getParent().addChild(appendIndex, loggingFunctionAttribute);
        // Replace the original attribute value to the placeholder.
        HtmlAttributeValueNode placeHolder =
            new HtmlAttributeValueNode(nodeIdGen.genId(), insertionLocation, Quotes.DOUBLE);
        placeHolder.addChild(
            new RawTextNode(
                nodeIdGen.genId(),
                ((LoggingFunction) function.getSoyFunction()).getPlaceholder(),
                insertionLocation));
        node.replaceChild(node.getChild(1), placeHolder);
        // We can break here since VeLogValidationPass guarantees that there is exactly one
        // logging function in a html attribute value.
        break;
      }
      visitChildrenAllowingConcurrentModification(node);
    }